Skip to content

Commit

Permalink
Merge pull request from GHSA-4w9v-7h8h-rv8x
Browse files Browse the repository at this point in the history
* migration to custom protocol

* Update prompt.js

* re-enable pretty URL display

* allow cross origin access from browser UI

* enable file protocol fuse

* enable fuses in dev environment

* don't use file protocol for NTP background

* migrations from file to internal protocol

* Fix update checks failing

The redirect to the new domain was causing the origin header to be dropped, which was causing the request to be treated as cross-origin and failing

* Update package.json

* Update minInternalProtocol.js
  • Loading branch information
PalmerAL authored Jan 8, 2024
1 parent ff1a185 commit 9efaf3b
Show file tree
Hide file tree
Showing 20 changed files with 172 additions and 67 deletions.
1 change: 1 addition & 0 deletions js/navbar/tabColor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var webviews = require('webviews.js')
var settings = require('util/settings/settings.js')

const colorExtractorImage = document.createElement('img')
colorExtractorImage.crossOrigin = 'anonymous'
const colorExtractorCanvas = document.createElement('canvas')
const colorExtractorContext = colorExtractorCanvas.getContext('2d')

Expand Down
44 changes: 23 additions & 21 deletions js/newTabPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,30 @@ const newTabPage = {
picker: document.getElementById('ntp-image-picker'),
deleteBackground: document.getElementById('ntp-image-remove'),
imagePath: path.join(window.globalArgs['user-data-path'], 'newTabBackground'),
blobInstance: null,
reloadBackground: function () {
newTabPage.background.src = newTabPage.imagePath + '?t=' + Date.now()
function onLoad () {
newTabPage.background.hidden = false
newTabPage.hasBackground = true
document.body.classList.add('ntp-has-background')
newTabPage.background.removeEventListener('load', onLoad)

newTabPage.deleteBackground.hidden = false
}
function onError () {
newTabPage.background.hidden = true
newTabPage.hasBackground = false
document.body.classList.remove('ntp-has-background')
newTabPage.background.removeEventListener('error', onError)

newTabPage.deleteBackground.hidden = true
}
newTabPage.background.addEventListener('load', onLoad)
newTabPage.background.addEventListener('error', onError)
fs.readFile(newTabPage.imagePath, function (err, data) {
if (newTabPage.blobInstance) {
URL.revokeObjectURL(newTabPage.blobInstance)
newTabPage.blobInstance = null
}
if (err) {
newTabPage.background.hidden = true
newTabPage.hasBackground = false
document.body.classList.remove('ntp-has-background')
newTabPage.deleteBackground.hidden = true
} else {
const blob = new Blob([data], { type: 'application/octet-binary' })
const url = URL.createObjectURL(blob)
newTabPage.blobInstance = url
newTabPage.background.src = url

newTabPage.background.hidden = false
newTabPage.hasBackground = true
document.body.classList.add('ntp-has-background')
newTabPage.deleteBackground.hidden = false
}
})
},
initialize: function () {
newTabPage.reloadBackground()
Expand Down Expand Up @@ -57,6 +61,4 @@ const newTabPage = {
}
}

window.ntp = newTabPage

module.exports = newTabPage
2 changes: 1 addition & 1 deletion js/pdfViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const urlParser = require('util/urlParser.js')

const PDFViewer = {
url: {
base: urlParser.getFileURL(__dirname + '/pages/pdfViewer/index.html'),
base: 'min://app/pages/pdfViewer/index.html',
queryString: '?url=%l'
},
isPDFViewer: function (tabId) {
Expand Down
4 changes: 2 additions & 2 deletions js/places/tagIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ var tagIndex = {
getPageTokens: function (page) {
var urlChunk = ''
try {
const url = new URL(page.url)
if (page.url.startsWith('file://') && url.searchParams.get('url')) {
let url = new URL(page.url)
if ((page.url.startsWith('file://') || page.url.startsWith('min://')) && url.searchParams.get('url')) {
url = new URL(url.searchParams.get('url'))
}
urlChunk = url.hostname.split('.').slice(0, -1).join(' ') + ' ' + url.pathname.split('/').filter(p => p.length > 1).slice(0, 2).join(' ')
Expand Down
2 changes: 1 addition & 1 deletion js/preload/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ ipc.on('enterPictureInPicture', function (event, data) {
})

window.addEventListener('message', function (e) {
if (!e.origin.startsWith('file://')) {
if (!e.origin.startsWith('min://')) {
return
}

Expand Down
2 changes: 1 addition & 1 deletion js/readerView.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var urlParser = require('util/urlParser.js')
var readerDecision = require('readerDecision.js')

var readerView = {
readerURL: urlParser.getFileURL(__dirname + '/reader/index.html'),
readerURL: 'min://app/reader/index.html',
getReaderURL: function (url) {
return readerView.readerURL + '?url=' + url
},
Expand Down
2 changes: 1 addition & 1 deletion js/searchbar/updateNotifications.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const UPDATE_URL = 'https://minbrowser.github.io/min/updates/latestVersion.json'
const UPDATE_URL = 'https://minbrowser.org/min/updates/latestVersion.json'

var settings = require('util/settings/settings.js')

Expand Down
2 changes: 1 addition & 1 deletion js/sessionRestore.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ const sessionRestore = {
// create a new tab with an explanation of what happened
var newTask = tasks.add()
var newSessionErrorTab = tasks.get(newTask).tabs.add({
url: 'file://' + __dirname + '/pages/sessionRestoreError/index.html?backupLoc=' + encodeURIComponent(backupSavePath)
url: 'min://app/pages/sessionRestoreError/index.html?backupLoc=' + encodeURIComponent(backupSavePath)
})

browserUI.switchToTask(newTask)
Expand Down
6 changes: 3 additions & 3 deletions js/util/settings/settingsPreload.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
window.addEventListener('message', function (e) {
if (!e.origin.startsWith('file://')) {
if (!e.origin.startsWith('min://')) {
return
}

Expand All @@ -13,7 +13,7 @@ window.addEventListener('message', function (e) {
})

ipc.on('receiveSettingsData', function (e, data) {
if (window.location.toString().startsWith('file://')) { // probably redundant, but might as well check
window.postMessage({ message: 'receiveSettingsData', settings: data }, 'file://')
if (window.location.toString().startsWith('min://')) { // probably redundant, but might as well check
window.postMessage({ message: 'receiveSettingsData', settings: data }, window.location.toString())
}
})
19 changes: 6 additions & 13 deletions js/util/urlParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,11 @@ var urlParser = {
return 'view-source:' + urlParser.parse(realURL)
}

// if the URL is an internal URL, convert it to the correct file:// url
if (url.startsWith('min:')) {
try {
var urlObj = new URL(url)
var pathname = urlObj.pathname.replace('//', '')
if (/^[a-zA-Z]+$/.test(pathname)) {
// only paths with letters are allowed
return urlParser.getFileURL(
path.join(__dirname, 'pages', pathname, 'index.html') + urlObj.search
)
}
} catch (e) {}
if (url.startsWith('min:') && !url.startsWith('min://app/')) {
// convert shortened min:// urls to full ones
const urlChunks = url.split('?')[0].replace(/min:(\/\/)?/g, '').split('/')
const query = url.split('?')[1]
return 'min://app/pages/' + urlChunks[0] + (urlChunks[1] ? urlChunks.slice(1).join('/') : '/index.html') + (query ? '?' + query : '')
}

// if the url starts with a (supported) protocol
Expand Down Expand Up @@ -111,7 +104,7 @@ var urlParser = {
}
},
isInternalURL: function (url) {
return url.startsWith(urlParser.getFileURL(__dirname))
return url.startsWith('min://')
},
getSourceURL: function (url) {
// converts internal URLs (like the PDF viewer or the reader view) to the URL of the page they are displaying
Expand Down
6 changes: 3 additions & 3 deletions js/webviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function captureCurrentTab (options) {

// called whenever a new page starts loading, or an in-page navigation occurs
function onPageURLChange (tab, url) {
if (url.indexOf('https://') === 0 || url.indexOf('about:') === 0 || url.indexOf('chrome:') === 0 || url.indexOf('file://') === 0) {
if (url.indexOf('https://') === 0 || url.indexOf('about:') === 0 || url.indexOf('chrome:') === 0 || url.indexOf('file://') === 0 || url.indexOf('min://') === 0) {
tabs.update(tab, {
secure: true,
url: url
Expand Down Expand Up @@ -104,7 +104,7 @@ const webviews = {
placeholderRequests: [],
asyncCallbacks: {},
internalPages: {
error: urlParser.getFileURL(__dirname + '/pages/error/index.html')
error: 'min://app/pages/error/index.html'
},
events: [],
IPCEvents: [],
Expand Down Expand Up @@ -464,7 +464,7 @@ webviews.bindIPC('setSetting', function (tabId, args) {
settings.listen(function () {
tasks.forEach(function (task) {
task.tabs.forEach(function (tab) {
if (tab.url.startsWith('file://')) {
if (tab.url.startsWith('min://')) {
try {
webviews.callAsync(tab.id, 'send', ['receiveSettingsData', settings.list])
} catch (e) {
Expand Down
22 changes: 21 additions & 1 deletion main/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ function downloadHandler (event, item, webContents) {
function listenForDownloadHeaders (ses) {
ses.webRequest.onHeadersReceived(function (details, callback) {
if (details.resourceType === 'mainFrame' && details.responseHeaders) {

let sourceWindow
if (details.webContents) {
const sourceView = Object.values(viewMap).find(view => view.webContents.id === details.webContents.id)
Expand Down Expand Up @@ -98,6 +97,27 @@ function listenForDownloadHeaders (ses) {
isFileView
})
}

/*
SECURITY POLICY EXCEPTION:
reader and PDF internal pages get universal access to web resources
Note: we can't limit to the URL in the query string, because there could be redirects
*/
if (details.webContents && (details.webContents.getURL().startsWith('min://app/pages/pdfViewer') || details.webContents.getURL().startsWith('min://app/reader/') || details.webContents.getURL() === 'min://app/index.html')) {
const filteredHeaders = Object.fromEntries(
Object.entries(details.responseHeaders).filter(([key, val]) => key.toLowerCase() !== 'access-control-allow-origin' && key.toLowerCase() !== 'access-control-allow-credentials')
)

callback({
responseHeaders: {
...filteredHeaders,
'Access-Control-Allow-Origin': 'min://app',
'Access-Control-Allow-Credentials': 'true'
}
})
return
}

callback({ cancel: false })
})
}
Expand Down
7 changes: 5 additions & 2 deletions main/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const {
crashReporter,
dialog,
nativeTheme,
shell
shell,
net
} = electron

crashReporter.start({
Expand Down Expand Up @@ -61,7 +62,7 @@ app.commandLine.appendSwitch('disable-backgrounding-occluded-windows', 'true')

var userDataPath = app.getPath('userData')

const browserPage = 'file://' + __dirname + '/index.html'
const browserPage = 'min://app/index.html'

var mainMenu = null
var secondaryMenu = null
Expand Down Expand Up @@ -327,6 +328,8 @@ app.on('ready', function () {
return
}

registerBundleProtocol(session.defaultSession)

const newWin = createWindow()

newWin.webContents.on('did-finish-load', function () {
Expand Down
2 changes: 1 addition & 1 deletion main/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function buildAppMenu (options = {}) {
accelerator: 'CmdOrCtrl+,',
click: function (item, window) {
sendIPCToWindow(window, 'addTab', {
url: 'file://' + __dirname + '/pages/settings/index.html'
url: 'min://app/pages/settings/index.html'
})
}
}
Expand Down
50 changes: 50 additions & 0 deletions main/minInternalProtocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const { pathToFileURL } = require('url')

protocol.registerSchemesAsPrivileged([
{
scheme: 'min',
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
}
}
])

function registerBundleProtocol (ses) {
ses.protocol.handle('min', (req) => {
let { host, pathname } = new URL(req.url)

if (pathname.charAt(0) === '/') {
pathname = pathname.substring(1)
}

if (host !== 'app') {
return new Response('bad', {
status: 400,
headers: { 'content-type': 'text/html' }
})
}

// NB, this checks for paths that escape the bundle, e.g.
// app://bundle/../../secret_file.txt
const pathToServe = path.resolve(__dirname, pathname)
const relativePath = path.relative(__dirname, pathToServe)
const isSafe = relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath)

if (!isSafe) {
return new Response('bad', {
status: 400,
headers: { 'content-type': 'text/html' }
})
}

return net.fetch(pathToFileURL(pathToServe).toString())
})
}

app.on('session-created', (ses) => {
if (ses !== session.defaultSession) {
registerBundleProtocol(ses)
}
})
2 changes: 1 addition & 1 deletion main/prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function createPrompt (options, callback) {
})

// Load the HTML dialog box
promptWindow.loadURL('file://' + __dirname + '/pages/prompt/index.html')
promptWindow.loadURL('min://app/pages/prompt/index.html')
promptWindow.once('ready-to-show', () => { promptWindow.show() })
}

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"author": "PalmerAL",
"version": "1.30.0",
"description": "A fast, minimal browser that protects your privacy",
"electronVersion": "29.0.0-alpha.4",
"electronVersion": "29.0.0-alpha.7",
"main": "main.build.js",
"standard": {
"globals": [
Expand All @@ -29,6 +29,7 @@
]
},
"dependencies": {
"@electron/fuses": "^1.7.0",
"dexie": "^3.0.3",
"dragula": "github:minbrowser/dragula",
"electron-squirrel-startup": "^1.0.0",
Expand All @@ -47,7 +48,7 @@
"browserify": "^16.5.1",
"concurrently": "^5.2.0",
"decomment": "^0.9.0",
"electron": "29.0.0-alpha.4",
"electron": "29.0.0-alpha.7",
"electron-builder": "^22.14.13",
"electron-installer-windows": "^3.0.0",
"electron-packager": "^15.1.0",
Expand All @@ -60,6 +61,7 @@
},
"license": "Apache-2.0",
"scripts": {
"postinstall": "node ./scripts/setupDevEnv.js",
"test": "standard --verbose js/**/*.js main/*.js",
"watch": "node ./scripts/watch.js",
"startElectron": "electron . --development-mode",
Expand Down
1 change: 1 addition & 0 deletions scripts/buildMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const modules = [
'main/touchbar.js',
'main/registryConfig.js',
'main/main.js',
'main/minInternalProtocol.js',
'js/util/settings/settingsMain.js',
'main/filtering.js',
'main/viewManager.js',
Expand Down
Loading

0 comments on commit 9efaf3b

Please sign in to comment.