diff --git a/web/packages/teleterm/src/services/config/configService.ts b/web/packages/teleterm/src/services/config/configService.ts index 2d97193850c17..2e153c59939de 100644 --- a/web/packages/teleterm/src/services/config/configService.ts +++ b/web/packages/teleterm/src/services/config/configService.ts @@ -23,7 +23,7 @@ import { createConfigStore } from './configStore'; const createAppConfigSchema = (platform: Platform) => { const defaultKeymap = getDefaultKeymap(platform); - const defaultFonts = getDefaultFonts(platform); + const defaultTerminalFont = getDefaultTerminalFont(platform); // Important: all keys except 'usageReporting.enabled' are currently not // configurable by the user. Before we let the user configure them, @@ -85,12 +85,14 @@ const createAppConfigSchema = (platform: Platform) => { 'keymap.openQuickInput': omitStoredConfigValue( z.string().default(defaultKeymap['open-quick-input']) ), - 'fonts.sansSerifFamily': omitStoredConfigValue( - z.string().default(defaultFonts['sansSerif']) - ), - 'fonts.monoFamily': omitStoredConfigValue( - z.string().default(defaultFonts['mono']) - ), + /** + * This value can be provided by the user and is unsanitized. This means that it cannot be directly interpolated + * in a styled component or used in CSS, as it may inject malicious CSS code. + * Before using it, sanitize it with `CSS.escape` or pass it as a `style` prop. + * Read more https://frontarm.com/james-k-nelson/how-can-i-use-css-in-js-securely/. + */ + 'terminal.fontFamily': z.string().default(defaultTerminalFont), + 'terminal.fontSize': z.number().int().min(1).max(256).default(15), }); }; @@ -189,23 +191,14 @@ const getDefaultKeymap = (platform: Platform) => { } }; -function getDefaultFonts(platform: Platform) { +function getDefaultTerminalFont(platform: Platform) { switch (platform) { case 'win32': - return { - sansSerif: "system-ui, 'Segoe WPC', 'Segoe UI', sans-serif", - mono: "'Consolas', 'Courier New', monospace", - }; + return "'Consolas', 'Courier New', monospace"; case 'linux': - return { - sansSerif: "system-ui, 'Ubuntu', 'Droid Sans', sans-serif", - mono: "'Droid Sans Mono', 'Courier New', monospace, 'Droid Sans Fallback'", - }; + return "'Droid Sans Mono', 'Courier New', monospace, 'Droid Sans Fallback'"; case 'darwin': - return { - sansSerif: '-apple-system, BlinkMacSystemFont, sans-serif', - mono: "Menlo, Monaco, 'Courier New', monospace", - }; + return "Menlo, Monaco, 'Courier New', monospace"; } } diff --git a/web/packages/teleterm/src/ui/App.tsx b/web/packages/teleterm/src/ui/App.tsx index ebd0a2ee1c56c..c96f35c28f72c 100644 --- a/web/packages/teleterm/src/ui/App.tsx +++ b/web/packages/teleterm/src/ui/App.tsx @@ -26,7 +26,7 @@ import { AppInitializer } from 'teleterm/ui/AppInitializer'; import CatchError from './components/CatchError'; import AppContextProvider from './appContextProvider'; import AppContext from './appContext'; -import ThemeProvider from './ThemeProvider'; +import { ThemeProvider } from './ThemeProvider'; export const App: React.FC<{ ctx: AppContext }> = ({ ctx }) => { return ( @@ -34,16 +34,7 @@ export const App: React.FC<{ ctx: AppContext }> = ({ ctx }) => { - + diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx b/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx index 14f860cc22593..a9219bcd80d0c 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx +++ b/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx @@ -25,7 +25,7 @@ import { import Document from 'teleterm/ui/Document'; import { useAppContext } from 'teleterm/ui/appContextProvider'; -import Terminal from './Terminal'; +import { Terminal } from './Terminal'; import DocumentReconnect from './DocumentReconnect'; import useDocTerminal, { Props } from './useDocumentTerminal'; import { useTshFileTransferHandlers } from './useTshFileTransferHandlers'; @@ -40,10 +40,15 @@ export default function DocumentTerminalContainer({ doc, visible }: Props) { export function DocumentTerminal(props: Props & { visible: boolean }) { const ctx = useAppContext(); + const { configService } = ctx.mainProcessClient; const { visible, doc } = props; const state = useDocTerminal(doc); const ptyProcess = state.data?.ptyProcess; const { upload, download } = useTshFileTransferHandlers(); + const unsanitizedTerminalFontFamily = configService.get( + 'terminal.fontFamily' + ).value; + const terminalFontSize = configService.get('terminal.fontSize').value; return ( diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.tsx b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.tsx index 34167f3f72a66..d6350fb927a6e 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.tsx +++ b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/Terminal.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React, { useEffect, useRef } from 'react'; -import styled, { useTheme } from 'styled-components'; +import styled from 'styled-components'; import { Box, Flex } from 'design'; import { debounce } from 'shared/utils/highbar'; @@ -23,15 +23,28 @@ import { IPtyProcess } from 'teleterm/sharedProcess/ptyHost'; import XTermCtrl from './ctrl'; -export default function Terminal(props: Props) { +type TerminalProps = { + ptyProcess: IPtyProcess; + visible: boolean; + /** + * This value can be provided by the user and is unsanitized. This means that it cannot be directly interpolated + * in a styled component or used in CSS, as it may inject malicious CSS code. + * Before using it, sanitize it with `CSS.escape` or pass it as a `style` prop. + * Read more https://frontarm.com/james-k-nelson/how-can-i-use-css-in-js-securely/. + */ + unsanitizedFontFamily: string; + fontSize: number; + onEnterKey?(): void; +}; + +export function Terminal(props: TerminalProps) { const refElement = useRef(); const refCtrl = useRef(); - const fontFamily = useTheme().fonts.mono; useEffect(() => { const ctrl = new XTermCtrl(props.ptyProcess, { el: refElement.current, - fontFamily, + fontSize: props.fontSize, }); ctrl.open(); @@ -70,17 +83,14 @@ export default function Terminal(props: Props) { width="100%" style={{ overflow: 'hidden' }} > - + ); } -type Props = { - ptyProcess: IPtyProcess; - visible: boolean; - onEnterKey?(): void; -}; - const StyledXterm = styled(Box)` height: 100%; width: 100%; diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts index 8685095084fc3..b4b8a13c9931d 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts +++ b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/ctrl.ts @@ -27,7 +27,7 @@ const WINDOW_RESIZE_DEBOUNCE_DELAY = 200; type Options = { el: HTMLElement; - fontFamily?: string; + fontSize: number; }; export default class TtyTerminal { @@ -51,7 +51,14 @@ export default class TtyTerminal { open(): void { this.term = new Terminal({ cursorBlink: false, - fontFamily: this.options.fontFamily, + /** + * `fontFamily` can be provided by the user and is unsanitized. This means that it cannot be directly used in CSS, + * as it may inject malicious CSS code. + * To sanitize the value, we set it as a style on the HTML element and then read it from it. + * Read more https://frontarm.com/james-k-nelson/how-can-i-use-css-in-js-securely/. + */ + fontFamily: this.el.style.fontFamily, + fontSize: this.options.fontSize, scrollback: 5000, theme: { background: theme.colors.primary.darker, diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/index.ts b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/index.ts index fc620f54061e9..23e3538925c91 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/index.ts +++ b/web/packages/teleterm/src/ui/DocumentTerminal/Terminal/index.ts @@ -14,6 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Terminal from './Terminal'; - -export default Terminal; +export { Terminal } from './Terminal'; diff --git a/web/packages/teleterm/src/ui/ThemeProvider/ThemeProvider.tsx b/web/packages/teleterm/src/ui/ThemeProvider/ThemeProvider.tsx index 6e037c7cba751..568f1b2b91cae 100644 --- a/web/packages/teleterm/src/ui/ThemeProvider/ThemeProvider.tsx +++ b/web/packages/teleterm/src/ui/ThemeProvider/ThemeProvider.tsx @@ -15,34 +15,21 @@ limitations under the License. */ import React from 'react'; -import { ThemeProvider, StyleSheetManager } from 'styled-components'; +import { + ThemeProvider as StyledThemeProvider, + StyleSheetManager, +} from 'styled-components'; import { GlobalStyle } from './globals'; import theme from './theme'; -type TeletermThemeProvider = { - fonts: { - mono: string; - sansSerif: string; - }; -}; - -const TeletermThemeProvider: React.FC = props => { - if (props?.fonts) { - theme.font = props.fonts.sansSerif; - theme.fonts = props.fonts; - } - - return ( - - - - - {props.children} - - - - ); -}; - -export default TeletermThemeProvider; +export const ThemeProvider: React.FC = props => ( + + + + + {props.children} + + + +); diff --git a/web/packages/teleterm/src/ui/ThemeProvider/index.ts b/web/packages/teleterm/src/ui/ThemeProvider/index.ts index f98fcd7e014e0..8820126cfdff8 100644 --- a/web/packages/teleterm/src/ui/ThemeProvider/index.ts +++ b/web/packages/teleterm/src/ui/ThemeProvider/index.ts @@ -14,5 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -import ThemeProvider from './ThemeProvider'; -export default ThemeProvider; +export { ThemeProvider } from './ThemeProvider'; diff --git a/web/packages/teleterm/src/ui/ThemeProvider/theme.ts b/web/packages/teleterm/src/ui/ThemeProvider/theme.ts index 2feed3ae1d6a2..7be2ca80023ec 100644 --- a/web/packages/teleterm/src/ui/ThemeProvider/theme.ts +++ b/web/packages/teleterm/src/ui/ThemeProvider/theme.ts @@ -114,11 +114,16 @@ const borders = [ '32px solid', ]; +const sansSerif = 'system-ui'; + const theme = { colors, typography, - font: fonts.sansSerif, - fonts: fonts, + font: sansSerif, + fonts: { + sansSerif, + mono: fonts.mono, + }, fontWeights, fontSizes, space,