How to get the path of a shell folder like "Local Settings" or "Local Appdata" for a specific user other than the current user?
While there are methods for getting special folder paths in Windows Script Host — WshShell.SpecialFolders and Shell.NameSpace — they return paths for the current user only. Getting other users' special folder paths is a bit tricky.
The proper way to do this is to use the Windows API SHGetKnownFolderPath function (or SHGetFolderPath on Windows versions prior to Vista). But the problem is, Windows Script Host doesn't support calling WinAPI functions, so to make use of these functions in your script you'll have to expose them via a custom-written COM component.
Another possible but undocumented solution is to read the special folder paths from that user's registry hive, specifically, the HKEY_USERS\<user_SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders key.
The paths in the User Shell Folders key are typically specified using the %USERPROFILE% environment variable; so to get fully-qualified paths you'll have to substitute this variable with the ProfileImagePath value from the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<user_SID> key.
Also, the HKEY_USERS\<user_SID> key is only available when the corresponding user is currently logged on. For a general solution, you would have to load the user's hive (<UserProfile>\ntuser.dat) into a temporary registry key (say, HKEY_USERS\Temp) and read values from this key instead.
Below is sample JScript code that demonstrates how your task can be accomplished. On Windows 7 and Vista, you may need to run the script as Administrator depending on your UAC settings.
NOTE: This method is discouraged, as Raymond Chen explains in his article The long and sad story of the Shell Folders key. There's no guarantee it will keep working in future versions of Windows.
var strUser = "foo";
var strDomain = "bar";
// If the account is local, domain name = computer name:
// var strDomain = getComputerName();
var strSID = getSID(strUser, strDomain);
var strProfilePath = getProfilePath(strSID);
// Load the user's registry hive into the HKEY_USERS\Temp key
var strTempKey = "Temp";
loadHKUHive(strTempKey, strProfilePath + "\\ntuser.dat");
// Get unexpanded path, e.g. %USERPROFILE%\AppData\Roaming
//var strAppData = getAppData(strSID);
var strAppData = getAppData(strTempKey);
WScript.Echo(strAppData);
// Expand the previous value to a fully-qualified path, e.g. C:\Users\foo\AppData\Roaming
strAppData = strAppData.replace(/%USERPROFILE%/i, strProfilePath);
WScript.Echo(strAppData);
// Unload the user's registry hive
unloadHKUHive(strTempKey);
function getComputerName() {
var oShell = new ActiveXObject("WScript.Shell");
return oShell.ExpandEnvironmentStrings("%COMPUTERNAME%");
}
function getSID(strUser, strDomain) {
var oAccount = GetObject("winmgmts:root/cimv2:Win32_UserAccount.Name='" + strUser + "',Domain='" + strDomain + "'");
return oAccount.SID;
}
function getProfilePath(strSID) {
var oShell = new ActiveXObject("WScript.Shell");
var strValue = oShell.RegRead("HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\" + strSID + "\\ProfileImagePath");
return strValue;
}
function getAppData(strSID) {
var oShell = new ActiveXObject("WScript.Shell");
var strValue = oShell.RegRead("HKEY_USERS\\" + strSID + "\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders\\AppData");
return strValue;
}
function loadHKUHive(strKeyName, strHiveFile) {
var oShell = new ActiveXObject("WScript.Shell");
oShell.Run("reg load HKU\\" + strKeyName + " " + strHiveFile, 0, true);
}
function unloadHKUHive(strKeyName) {
var oShell = new ActiveXObject("WScript.Shell");
oShell.Run("reg unload HKU\\" + strKeyName, 0, true);
}
Related
I like to write a Thunderbird AddOn that encrypts stuff. For this, I already extracted all data from the compose window. Now I have to save this into files and run a local executable for encryption. But I found no way to save the files and execute an executable on the local machine. How can I do that?
I found the File and Directory Entries API documentation, but it seems to not work. I always get undefined while trying to get the object with this code:
var filesystem = FileSystemEntry.filesystem;
console.log(filesystem); // --> undefined
At least, is there a working AddOn that I can examine to find out how this is working and maybe what permissions I have to request in the manifest.json?
NOTE: Must work cross-platform (Windows and Linux).
The answer is, that WebExtensions are currently not able to execute local files. Also, saving to some local folder on the disk is also not possible.
Instead, you need to add some WebExtension Experiment to your project and there use the legacy APIs. There you can use the IOUtils and FileUtils extensions to reach your goal:
Execute a file:
In your background JS file:
var ret = await browser.experiment.execute("/usr/bin/executable", [ "-v" ]);
In the experiment you can execute like this:
var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
var { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["IOUtils");
async execute(executable, arrParams) {
var fileExists = await IOUtils.exists(executable);
if (!fileExists) {
Services.wm.getMostRecentWindow("mail:3pane")
.alert("Executable [" + executable + "] not found!");
return false;
}
var progPath = new FileUtils.File(executable);
let process = Cc["#mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
process.init(progPath);
process.startHidden = false;
process.noShell = true;
process.run(true, arrParams, arrParams.length);
return true;
},
Save an attachment to disk:
In your backround JS file you can do like this:
var f = messenger.compose.getAttachmentFile(attachment.id)
var blob = await f.arrayBuffer();
var t = await browser.experiment.writeFileBinary(tempFile, blob);
In the experiment you can then write the file like this:
async writeFileBinary(filename, data) {
// first we need to convert the arrayBuffer to some Uint8Array
var uint8 = new Uint8Array(data);
uint8.reduce((binary, uint8) => binary + uint8.toString(2), "");
// then we can save it
var ret = await IOUtils.write(filename, uint8);
return ret;
},
IOUtils documentation:
https://searchfox.org/mozilla-central/source/dom/chrome-webidl/IOUtils.webidl
FileUtils documentation:
https://searchfox.org/mozilla-central/source/toolkit/modules/FileUtils.jsm
So I wanted to use the $.now() along with the persons IP address as a type of random number generator to attach to the front of file names to insure that if a file with the same name was uploaded it would not overwrite the first one.
the code I came up with was as follows:
var ipadd = '';
var filename = 'name';
var militime = 't';
var uploadname = '';
var ipid = '';
$(window).load(function() {
militime = $.now();
console.log(militime);
$('a.next-btn').on('click', function() {
$.get("https://ipinfo.io", function(response) {
ipadd = response.ip;
ipid = ipadd + militime;
}, "jsonp");
});
$('.file-uploader-form input[name="floor_plan_upload"]').live('change', function() {
var filename = $('.flow-section input[name="floor_plan_upload"]').val().split('\\').pop();
uploadname = ipid + filename;
$('input[name="file_path"]').val(uploadname).change();
});
});
I tested it in chrome on mac and it worked fine, it also works fine in firefox on windows but not chrome or microsoft edge on windows, it only displays the IP address.
Ive also tryed using Date.now(); and new Date().getTime(); to the same ends.
I did notice though that when I remove the militime = $.now(); from after the $(window).load(function(){ that it doesn't even print what I made the default value for militime 't', so that suggests that there is something else wrong here, I just don't know what.
You should not use the user provided filename as the filename on the server. So, generate the unique filename on the server instead.
OK this doesn't solve the problem, but then again you're solving it wrong...
When I published the app, in dev mode the spreadsheet is getting updated, but in exec mode it is not, even though the spreadsheet has all the permission for any anonymous to edit it.
//Code.js:
function doGet(e) {
return HtmlService.createTemplateFromFile('Form.html')
.evaluate() // evaluate MUST come before setting the NATIVE mode
.setTitle('Name To Appear in Browser Tab')
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
}
function processForm(theForm) {
var fileBlob = theForm.picToLoad;
var fldrSssn = DocsList.getFolder('For Web Hosting');
var createdFile = fldrSssn.createFile(fileBlob);
var fileUrl = createdFile.getUrl();
var name = theForm.user_name;
var files = DocsList.find('prat');// this is an array of file objects that include the term 'thisisthesheetIwant'
var file = files[0];// I take the first one
var ss = SpreadsheetApp.open(file);// using that file object I can open a spreadsheet
var sheet = ss.getSheets()[0];
var lastRow = sheet.getLastRow();
sheet.getRange(lastRow+1, 1, 1, 2).setValues([[name,fileUrl]]);
return true;
}
If you haven't re-published a new version, it will keep running the old version in exec mode. Dev mode runs the current version. Exec mode runs the last published version
I get a PKCS#7 crypto package from a 3rd party system.
The package is not compressed and not encrypted, PEM-encoded, signed with X.509 certificate.
I also have a PEM cert file from the provider.
The data inside is XML
I need to do the following in Node.JS:
extract the data
verify the signature
A sample package (no sensitive info, data refers to our qa system) http://pastebin.com/7ay7F99e
OK, finally got it.
First of all, PKCS messages are complex structures binary-encoded using ASN1.
Second, they can be serialized to binary files (DER encoding) or text PEM files using Base64 encoding.
Third, PKCS#7 format specifies several package types from which my is called Signed Data. These formats are distinguished by OBJECT IDENTIFIER value in the beginning of the ASN1 object (1st element of the wrapper sequence) — you can go to http://lapo.it/asn1js/ and paste the package text for the fully parsed structure.
Next, we need to parse the package (Base64 -> ASN1 -> some object representation). Unfortunately, there's no npm package for that. I found quite a good project forge that is not published to npm registry (though npm-compatible). It parsed PEM format but the resulting tree is quite an unpleasant thing to traverse. Based on their Encrypted Data and Enveloped Data implementations I created partial implementation of Signed Data in my own fork. UPD: my pull request was later merged to the forge project.
Now finally we have the whole thing parsed.
At that point I found a great (and probably the only on the whole web) explanative article on signed PKCS#7 verification: http://qistoph.blogspot.com/2012/01/manual-verify-pkcs7-signed-data-with.html
I was able to extract and successfully decode the signature from the file, but the hash inside was different from the data's hash. God bless Chris who explained what actually happens.
The data signing process is 2-step:
original content's hash is calculated
a set of "Authorized Attributes" is constructed including: type of the data singed, signing time and data hash
Then the set from step 2 is signed using the signer's private key.
Due to PKCS#7 specifics this set of attributes is stored inside of the context-specific constructed type (class=0x80, type=0) but should be signed and validated as normal SET (class=0, type=17).
As Chris mentions (https://stackoverflow.com/a/16154756/108533) this only verifies that the attributes in the package are valid. We should also validate the actual data hash against the digest attribute.
So finally here's a code doing validation (cert.pem is a certificate file that the provider sent me, package is a PEM-encoded message I got from them over HTTP POST):
var fs = require('fs');
var crypto = require('crypto');
var forge = require('forge');
var pkcs7 = forge.pkcs7;
var asn1 = forge.asn1;
var oids = forge.pki.oids;
var folder = '/a/path/to/files/';
var pkg = fs.readFileSync(folder + 'package').toString();
var cert = fs.readFileSync(folder + 'cert.pem').toString();
var res = true;
try {
var msg = pkcs7.messageFromPem(pkg);
var attrs = msg.rawCapture.authenticatedAttributes;
var set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs);
var buf = Buffer.from(asn1.toDer(set).data, 'binary');
var sig = msg.rawCapture.signature;
var v = crypto.createVerify('RSA-SHA1');
v.update(buf);
if (!v.verify(cert, sig)) {
console.log('Wrong authorized attributes!');
res = false;
}
var h = crypto.createHash('SHA1');
var data = msg.rawCapture.content.value[0].value[0].value;
h.update(data);
var attrDigest = null;
for (var i = 0, l = attrs.length; i < l; ++i) {
if (asn1.derToOid(attrs[i].value[0].value) === oids.messageDigest) {
attrDigest = attrs[i].value[1].value[0].value;
}
}
var dataDigest = h.digest();
if (dataDigest !== attrDigest) {
console.log('Wrong content digest');
res = false;
}
}
catch (_e) {
console.dir(_e);
res = false;
}
if (res) {
console.log("It's OK");
}
Your answer is a big step in the right direction. You are however missing out an essential part of the validation!
You should verify the hash of the data against the digest contained in the signed attributes. Otherwise it would be possible for someone to replace the content with malicious data. Try for example validating the following 'package' with your code (and have a look at the content): http://pastebin.com/kaZ2XQQc
I'm not much of a NodeJS developer (this is actually my first try :p), but here's a suggestion to help you get started.
var fs = require('fs');
var crypto = require('crypto');
var pkcs7 = require('./js/pkcs7'); // forge from my own fork
var asn1 = require('./js/asn1');
var folder = '';
var pkg = fs.readFileSync(folder + 'package').toString();
var cert = fs.readFileSync(folder + 'cert.pem').toString();
try {
var msg = pkcs7.messageFromPem(pkg);
var attrs = msg.rawCapture.authenticatedAttributes; // got the list of auth attrs
var set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs); // packed them inside of the SET object
var buf = new Buffer(asn1.toDer(set).data, 'binary'); // DO NOT forget 'binary', otherwise it tries to interpret bytes as UTF-8 chars
var sig = msg.rawCapture.signature;
var shasum = crypto.createHash('sha1'); // better be based on msg.rawCapture.digestAlgorithms
shasum.update(msg.rawCapture.content.value[0].value[0].value);
for(var n in attrs) {
var attrib = attrs[n].value;
var attrib_type = attrib[0].value;
var attrib_value = attrib[1].value[0].value;
if(attrib_type == "\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04") { // better would be to use the OID (1.2.840.113549.1.9.4)
if(shasum.digest('binary') == attrib_value) {
console.log('hash matches');
var v = crypto.createVerify('RSA-SHA1');
v.update(buf);
console.log(v.verify(cert, sig)); // -> should type true
} else {
console.log('hash mismatch');
}
}
}
}
catch (_e) {
console.dir(_e);
}
based on inspiration form this answer, I've implemented a sample for signing and verifying pdf files using node-signpdf and node-forge.
How can I tell in JavaScript what path separator is used in the OS where the script is running?
Use path module in node.js returns the platform-specific file separator.
example
path.sep // on *nix evaluates to a string equal to "/"
Edit: As per Sebas's comment below, to use this, you need to add this at the top of your js file:
const path = require('path')
Afair you can always use / as a path separator, even on Windows.
Quote from http://bytes.com/forum/thread23123.html:
So, the situation can be summed up
rather simply:
All DOS services since DOS 2.0 and all Windows APIs accept either forward
slash or backslash. Always have.
None of the standard command shells (CMD or COMMAND) will accept forward
slashes. Even the "cd ./tmp" example
given in a previous post fails.
The Correct Answer
Yes all OS's accept CD ../ or CD ..\ or CD .. regardless of how you pass in separators. But what about reading a path back. How would you know if its say, a 'windows' path, with ' ' and \ allowed.
The Obvious 'Duh!' Question
What happens when you depend on, for example, the installation directory %PROGRAM_FILES% (x86)\Notepad++. Take the following example.
var fs = require('fs'); // file system module
var targetDir = 'C:\Program Files (x86)\Notepad++'; // target installer dir
// read all files in the directory
fs.readdir(targetDir, function(err, files) {
if(!err){
for(var i = 0; i < files.length; ++i){
var currFile = files[i];
console.log(currFile);
// ex output: 'C:\Program Files (x86)\Notepad++\notepad++.exe'
// attempt to print the parent directory of currFile
var fileDir = getDir(currFile);
console.log(fileDir);
// output is empty string, ''...what!?
}
}
});
function getDir(filePath){
if(filePath !== '' && filePath != null){
// this will fail on Windows, and work on Others
return filePath.substring(0, filePath.lastIndexOf('/') + 1);
}
}
What happened!?
targetDir is being set to a substring between the indices 0, and 0 (indexOf('/') is -1 in C:\Program Files\Notepad\Notepad++.exe), resulting in the empty string.
The Solution...
This includes code from the following post: How do I determine the current operating system with Node.js
myGlobals = { isWin: false, isOsX:false, isNix:false };
Server side detection of OS.
// this var could likely a global or available to all parts of your app
if(/^win/.test(process.platform)) { myGlobals.isWin=true; }
else if(process.platform === 'darwin'){ myGlobals.isOsX=true; }
else if(process.platform === 'linux') { myGlobals.isNix=true; }
Browser side detection of OS
var appVer = navigator.appVersion;
if (appVer.indexOf("Win")!=-1) myGlobals.isWin = true;
else if (appVer.indexOf("Mac")!=-1) myGlobals.isOsX = true;
else if (appVer.indexOf("X11")!=-1) myGlobals.isNix = true;
else if (appVer.indexOf("Linux")!=-1) myGlobals.isNix = true;
Helper Function to get the separator
function getPathSeparator(){
if(myGlobals.isWin){
return '\\';
}
else if(myGlobals.isOsx || myGlobals.isNix){
return '/';
}
// default to *nix system.
return '/';
}
// modifying our getDir method from above...
Helper function to get the parent directory (cross platform)
function getDir(filePath){
if(filePath !== '' && filePath != null){
// this will fail on Windows, and work on Others
return filePath.substring(0, filePath.lastIndexOf(getPathSeparator()) + 1);
}
}
getDir() must be intelligent enough to know which its looking for.
You can get even really slick and check for both if the user is inputting a path via command line, etc.
// in the body of getDir() ...
var sepIndex = filePath.lastIndexOf('/');
if(sepIndex == -1){
sepIndex = filePath.lastIndexOf('\\');
}
// include the trailing separator
return filePath.substring(0, sepIndex+1);
You can also use 'path' module and path.sep as stated above, if you want to load a module to do this simple of a task. Personally, i think it sufficient to just check the information from the process that is already available to you.
var path = require('path');
var fileSep = path.sep; // returns '\\' on windows, '/' on *nix
And Thats All Folks!
As already answered here, you can find the OS specific path separator with path.sep to manually construct your path. But you can also let path.join do the job, which is my preferred solution when dealing with path constructions:
Example:
const path = require('path');
const directory = 'logs';
const file = 'data.json';
const path1 = `${directory}${path.sep}${file}`;
const path2 = path.join(directory, file);
console.log(path1); // Shows "logs\data.json" on Windows
console.log(path2); // Also shows "logs\data.json" on Windows
Just use "/", it works on all OS's as far as I know.