Skip to content

Commit

Permalink
[desktop] changes after review with ivk
Browse files Browse the repository at this point in the history
  • Loading branch information
ganthern authored and charlag committed Dec 21, 2021
1 parent d45a53e commit ce68483
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 67 deletions.
30 changes: 17 additions & 13 deletions src/desktop/DesktopUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {log} from "./DesktopLog"
import {DesktopCryptoFacade} from "./DesktopCryptoFacade"
import {fileExists, swapFilename} from "./PathUtils"
import url from "url"
import {registerKeys, unregisterKeys} from "./reg-templater"

export class DesktopUtils {

Expand Down Expand Up @@ -240,24 +241,27 @@ export class DesktopUtils {
const logPath = path.join(appData, 'Roaming', app.getName(), 'logs')
const tmpPath = path.join(appData, "Local", "Temp", this._topLevelDownloadDir, "attach")
const isLocal = await this.checkIsPerUserInstall()
const tmpRegScript = (await import('./reg-templater.js')).registerKeys(
execPath,
dllPath,
logPath,
tmpPath,
isLocal
)
return this._executeRegistryScript(tmpRegScript)
.then(() => app.setAsDefaultProtocolClient('mailto'))
.then(() => this._electron.shell.openExternal('ms-settings:defaultapps').catch())
const tmpRegScript = registerKeys({execPath, dllPath, logPath, tmpPath}, isLocal)
await this._executeRegistryScript(tmpRegScript)
app.setAsDefaultProtocolClient('mailto')
await this._openDefaultAppsSettings()
}

async _unregisterOnWin(): Promise<void> {
app.removeAsDefaultProtocolClient('mailto')
const isLocal = await this.checkIsPerUserInstall()
const tmpRegScript = (await import('./reg-templater.js')).unregisterKeys(isLocal)
return this._executeRegistryScript(tmpRegScript)
.then(() => this._electron.shell.openExternal('ms-settings:defaultapps').catch())
const tmpRegScript = unregisterKeys(isLocal)
await this._executeRegistryScript(tmpRegScript)
await this._openDefaultAppsSettings()
}

async _openDefaultAppsSettings(): Promise<void> {
try {
await this._electron.shell.openExternal('ms-settings:defaultapps')
} catch (e) {
// ignoring, this is just a convenience for the user
console.error("failed to open default apps settings page:", e.message)
}
}

/**
Expand Down
54 changes: 34 additions & 20 deletions src/desktop/integration/RegistryScriptGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,19 @@
*
* * there is a header line followed by a list of sections
* * each section starts with a path in square brackets []. if the path is prefixed with a dash (-),
* that path and all subkeys will be removed.
* * sections can contain default and named value assignments of the form (NAME|@)=(VALUE|-).
* if the value is a dash (-) the name will be removed or the default value will be unset.
* that path and all its subkeys will be removed.
* * sections can contain
* * default value assignments of the form @="VALUE"
* * default value nullifications of the form @=-
* * named value assignments of the form "NAME"="VALUE"
* * named value deletions of the form "NAME"=-
*
* The script generator uses JavaScript arrays of RegistryValueTemplates as templates.
* During application, RegistryValueTemplates will recursively written to the registry at their respective root path.
* The removal script preserves the root keys. this means that subkeys that were created in a root key will be recursively deleted,
* but string values that are assigned directly to a root key or a name in that subkey will only be nulled. Example:
*
* ```
* const template = [{
* root: "HKLM\\SOFTWARE\\CLIENTS\\MAIL",
* value: {"named": "val", subkey: {"": "default_value_2", "DLLPath": "C:\\dll\\path\\t.dll",}, "": "default_value_1"}
* }]
* ```
*
* Will result in the application script
* To generate this application script:
* ```
* Windows Registry Editor Version 5.00
* [HKLM\SOFTWARE\CLIENTS\MAIL]
Expand All @@ -52,7 +48,7 @@
* "DLLPath"="C:\dll\path\t.dll"
* ```
*
* and the removal script
* and this removal script
*
* ```
* Windows Registry Editor Version 5.00
Expand All @@ -64,24 +60,42 @@
* [-HKLM\SOFTWARE\CLIENTS\MAIL\subkey]
* ```
*
* Note that "HKLM\SOFTWARE\CLIENTS\MAIL\subkey" was removed entirely while the values
* We would use this template. Note the values with empty keys which get expanded to @=<value> assignments.
*
* ```
* const template = [{
* root: "HKLM\\SOFTWARE\\CLIENTS\\MAIL",
* value: {"named": "val", subkey: {"": "default_value_2", "DLLPath": "C:\\dll\\path\\t.dll",}, "": "default_value_1"}
* }]
* ```
*
* Also note that "HKLM\SOFTWARE\CLIENTS\MAIL\subkey" was removed entirely while the values
* directly assigned to "HKLM\SOFTWARE\CLIENTS\MAIL" are only nulled, because that path was given as a root.
*
* Current Limitations:
* * only string values are supported
* * application can only write, removal will only remove
* */

export type RegistryTemplateDefinition = Array<RegistryValueTemplate>
export type RegistryTemplateDefinition = $ReadOnlyArray<RegistryValueTemplate>
export type RegistryValueTemplate = {value: RegistrySubKey, root: string}
export type RegistrySubKey = {[string]: RegistryValue}
export type RegistryValue = RegistrySubKey | string
type OperationBuffer = {[string]: Array<string>}

const header_line = "Windows Registry Editor Version 5.00"
const quote = s => `"${s}"`
const keyLine = path => `[${path}]`
const valueLine = (path, value) => `${path === "" ? "@" : quote(path)}=${value == null ? "-" : quote(value)}`

function quote(s: string): string {
return `"${s}"`
}

function keyLine(path: string): string {
return `[${path}]`
}

function valueLine(path: string, value: ?string): string {
return `${path === "" ? "@" : quote(path)}=${value == null ? "-" : quote(value)}`
}

/**
* value expander for the script generators. if a value is not a string, it's another section
Expand Down Expand Up @@ -132,22 +146,22 @@ function bufToScript(buf: OperationBuffer): string {
/**
* the application and removal script generators are very similar in structure, this function abstracts over that.
*/
function scriptBuilder(remove: boolean, template: Array<RegistryValueTemplate>): string {
function scriptBuilder(remove: boolean, template: RegistryTemplateDefinition): string {
const buf = template.reduce((prev, {root, value}) => expandSection(root, value, prev, remove), {})
return bufToScript(buf)
}

/**
* create a windows registry script that can be executed to apply the given template
*/
export function applyScriptBuilder(template: Array<RegistryValueTemplate>): string {
export function applyScriptBuilder(template: RegistryTemplateDefinition): string {
return scriptBuilder(false, template)
}

/**
* create a windows registry script that can be executed to remove the values that have been
* created by executing the script generated from the template by applyScriptBuilder
*/
export function removeScriptBuilder(template: Array<RegistryValueTemplate>): string {
export function removeScriptBuilder(template: RegistryTemplateDefinition): string {
return scriptBuilder(true, template)
}
73 changes: 47 additions & 26 deletions src/desktop/reg-templater.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,26 @@ const esc = s => s.replace(/\\/g, '\\\\')
const hklm = "HKEY_LOCAL_MACHINE"
const hkcu = "HKEY_CURRENT_USER"
const hkcr = "HKEY_CLASSES_ROOT"
const sw_clients_mail = r => `${r}\\SOFTWARE\\Clients\\Mail`
const sw_cls = r => `${r}\\SOFTWARE\\CLASSES`
const sw = r => `${r}\\SOFTWARE`
const sw_wow = r => `${r}\\SOFTWARE\\Wow6432Node`
const sw_reg_apps = r => `${r}\\SOFTWARE\\RegisteredApplications`
const sw_wow_reg_apps = r => `${r}\\SOFTWARE\\Wow6432Node\\RegisteredApplications`

/**
* get a registry template specific to tutanota desktop
* https://docs.microsoft.com/en-us/windows/win32/msi/installation-context#registry-redirection
* execPath: path for the dll to this executable
* dllPath: path for other apps to the dll
* logPath: path for the dll to log mapi activity to
* tmpPath: path for the dll to put temporary attachment files
*/
function getTemplate(
export type RegistryPaths = {
execPath: string,
dllPath: string,
logPath: string,
tmpPath: string,
local: boolean,
): RegistryTemplateDefinition {
tmpPath: string
}

/**
* get a registry template specific to tutanota desktop
* https://docs.microsoft.com/en-us/windows/win32/msi/installation-context#registry-redirection
*/
function getTemplate(opts: RegistryPaths, local: boolean): RegistryTemplateDefinition {
const {execPath, dllPath, logPath, tmpPath} = opts
const client_template = {
tutanota: {
"": "tutanota",
Expand Down Expand Up @@ -64,27 +66,46 @@ function getTemplate(

const r = local ? hkcu : hklm
return [
{root: sw_clients_mail(r), value: client_template},
{root: sw_cls(r), value: {mailto: mailto_template, "tutanota.Mailto": mailto_template}},
{root: hkcr, value: {mailto: mailto_template, "tutanota.Mailto": mailto_template}},
{root: sw_reg_apps(r), value: {"tutanota": "SOFTWARE\\\\tutao\\\\tutanota\\\\Capabilities"}},
{root: sw_wow_reg_apps(r), value: {"tutanota": "SOFTWARE\\\\Wow6432Node\\\\tutao\\\\tutanota\\\\Capabilities"}},
{root: sw(r), value: capabilities_template},
{root: sw_wow(r), value: capabilities_template}
{
root: `${r}\\SOFTWARE\\Clients\\Mail`,
value: client_template
},
{
root: `${r}\\SOFTWARE\\CLASSES`,
value: {mailto: mailto_template, "tutanota.Mailto": mailto_template}
},
{
root: hkcr,
value: {mailto: mailto_template, "tutanota.Mailto": mailto_template}
},
{
root: `${r}\\SOFTWARE\\RegisteredApplications`,
value: {"tutanota": "SOFTWARE\\\\tutao\\\\tutanota\\\\Capabilities"}
},
{
root: `${r}\\SOFTWARE\\Wow6432Node\\RegisteredApplications`,
value: {"tutanota": "SOFTWARE\\\\Wow6432Node\\\\tutao\\\\tutanota\\\\Capabilities"}
},
{
root: `${r}\\SOFTWARE`,
value: capabilities_template
},
{
root: `${r}\\SOFTWARE\\Wow6432Node`,
value: capabilities_template
}
]
}

/**
* produce a tmp windows registry script to register an executable as a mailto handler
* @param execPath path to the executable that should be registered
* @param dllPath path to the mapi dll that handles "Send as Mail..." requests
* @param logPath path to the directory the mapi dll should put logs in
* @param tmpPath path to the tmp dir that's managed by tutanota
* @param opts {RegistryPaths}
* @param local set to true if the app was installed per-user
* @returns {string} registry script
*/
export function registerKeys(execPath: string, dllPath: string, logPath: string, tmpPath: string, local: boolean): string {
const template = getTemplate(esc(execPath), esc(dllPath), esc(logPath), esc(tmpPath), local)
export function registerKeys(opts: RegistryPaths, local: boolean): string {
const {execPath, dllPath, logPath, tmpPath} = opts
const template = getTemplate({execPath: esc(execPath), dllPath: esc(dllPath), logPath: esc(logPath), tmpPath: esc(tmpPath)}, local)
return applyScriptBuilder(template)
}

Expand All @@ -94,6 +115,6 @@ export function registerKeys(execPath: string, dllPath: string, logPath: string,
*/
export function unregisterKeys(local: boolean): string {
// the removal script generator doesn't care about values
const template = getTemplate("execPath", "dllPath", "logPath", "tmpPath", local)
const template = getTemplate({execPath: "execPath", dllPath: "dllPath", logPath: "logPath", tmpPath: "tmpPath"}, local)
return removeScriptBuilder(template)
}
16 changes: 8 additions & 8 deletions test/client/desktop/integration/RegistryScriptGeneratorTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,40 @@ o.spec("RegistryScriptGenerator Test", function () {
const templates = [
{
name: "empty template",
t: [],
template: [],
apply: "Windows Registry Editor Version 5.00",
remove: "Windows Registry Editor Version 5.00"
},
{
name: "naked value",
t: [{root: 'HKLM', value: {"tuta": "some_value"}}],
template: [{root: 'HKLM', value: {"tuta": "some_value"}}],
apply: 'Windows Registry Editor Version 5.00\r\n\r\n[HKLM]\r\n"tuta"="some_value"',
remove: 'Windows Registry Editor Version 5.00\r\n\r\n[HKLM]\r\n"tuta"=-'
},
{
name: "with subkeys",
t: [{root: 'HKLM', value: {tuta: {'': "subkey_value"}}}],
template: [{root: 'HKLM', value: {tuta: {'': "subkey_value"}}}],
apply: 'Windows Registry Editor Version 5.00\r\n\r\n[HKLM\\tuta]\r\n@="subkey_value"',
remove: 'Windows Registry Editor Version 5.00\r\n\r\n[-HKLM\\tuta]'
},
{
name: "mixed",
t: [{root: "HKLM", value: {direct: "direct_val", sub: {"key": "sub_val"}}}],
template: [{root: "HKLM", value: {direct: "direct_val", sub: {"key": "sub_val"}}}],
apply: 'Windows Registry Editor Version 5.00\r\n\r\n[HKLM]\r\n"direct"="direct_val"\r\n\r\n[HKLM\\sub]\r\n"key"="sub_val"',
remove: 'Windows Registry Editor Version 5.00\r\n\r\n[HKLM]\r\n"direct"=-\r\n\r\n[-HKLM\\sub]',
},
{
name: "deeper subkey",
t: [{root: "HKCU", value: {a: {deep: {path: {subkey: {'': "hello."}}}}}}],
template: [{root: "HKCU", value: {a: {deep: {path: {subkey: {'': "hello."}}}}}}],
apply: 'Windows Registry Editor Version 5.00\r\n\r\n[HKCU\\a\\deep\\path\\subkey]\r\n@="hello."',
remove: 'Windows Registry Editor Version 5.00\r\n\r\n[-HKCU\\a]',
},
]

for (const {t, apply, remove, name} of templates) {
for (const {template, apply, remove, name} of templates) {
o(name, function () {
o(applyScriptBuilder(t)).equals(apply)
o(removeScriptBuilder(t)).equals(remove)
o(applyScriptBuilder(template)).equals(apply)
o(removeScriptBuilder(template)).equals(remove)
})
}
})

0 comments on commit ce68483

Please sign in to comment.