Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion assets/js/exelearning-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,13 @@
const iframeWindow = this.iframe[0]?.contentWindow;

if ( ! data || ! data.type || ! iframeWindow || event.source !== iframeWindow ) {
if ( data?.source === 'wp-exe-editor' && data.type === 'request-save' ) {
// The standalone editor page relays Ctrl+S through its own window.
if (
data?.source === 'wp-exe-editor' &&
data.type === 'request-save' &&
event.source === window &&
event.origin === window.location.origin
) {
this.requestSave();
}
return;
Expand Down
4 changes: 4 additions & 0 deletions assets/js/wp-exe-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@
if ( ! message.type || message.source === 'wp-exe-editor' ) {
return;
}
// Only the embedding WordPress page may drive save/export.
if ( event.source !== window.parent || ( '*' !== targetOrigin && event.origin !== targetOrigin ) ) {
return;
}

try {
switch ( message.type ) {
Expand Down
11 changes: 11 additions & 0 deletions tests/js/exelearning_editor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,17 @@ describe( 'exelearning-editor: the conversation with the editor', () => {
expect( messagesOfType( posted, 'WP_REQUEST_SAVE' ) ).toHaveLength( 1 );
} );

it( 'ignores a save request relayed from another window or origin', async () => {
const editor = await loadEditorOn( MODAL_MARKUP );
const posted = stubEditorWindow( editor );
const data = { source: 'wp-exe-editor', type: 'request-save' };

await editor.handleMessage( { data, source: {}, origin: window.location.origin } );
await editor.handleMessage( { data, source: window, origin: 'https://evil.test' } );

expect( messagesOfType( posted, 'WP_REQUEST_SAVE' ) ).toHaveLength( 0 );
} );

it( 'asks the editor for the file when the save button is pressed', async () => {
const editor = await loadEditorOn( MODAL_MARKUP );
const posted = stubEditorWindow( editor );
Expand Down
33 changes: 31 additions & 2 deletions tests/js/wp_exe_bridge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ async function settle() {
}

/** Deliver a protocol message from the parent and let the bridge answer it. */
async function send( data ) {
window.dispatchEvent( new window.MessageEvent( 'message', { data } ) );
async function send( data, from = window.parent, origin = '' ) {
const event = new window.MessageEvent( 'message', { data, origin } );
Object.defineProperty( event, 'source', { value: from } );
window.dispatchEvent( event );
await settle();
}

Expand Down Expand Up @@ -250,6 +252,33 @@ describe( 'wp-exe-bridge: announcing itself to the parent', () => {
} );
} );

describe( 'wp-exe-bridge: who may drive the protocol', () => {
it( 'ignores protocol commands from a window other than the parent', async () => {
embedIn( { postMessage: ( message ) => posted.push( message ) } );
installEditorWithDocument();
await loadBridge();
posted.length = 0;

await send( { type: 'GET_PROJECT_INFO', requestId: 'x' }, window );

expect( messageOf( 'PROJECT_INFO' ) ).toBeUndefined();
} );

it( 'ignores protocol commands from another origin once the parent origin is known', async () => {
embedIn( { postMessage: ( message ) => posted.push( message ) } );
window.__EXE_EMBEDDING_CONFIG__ = { parentOrigin: 'https://wp.example' };
installEditorWithDocument();
await loadBridge();
posted.length = 0;

await send( { type: 'GET_PROJECT_INFO', requestId: 'x' }, window.parent, 'https://evil.test' );
expect( messageOf( 'PROJECT_INFO' ) ).toBeUndefined();

await send( { type: 'GET_PROJECT_INFO', requestId: 'y' }, window.parent, 'https://wp.example' );
expect( messageOf( 'PROJECT_INFO' ) ).toBeDefined();
} );
} );

describe( 'wp-exe-bridge: the target origin', () => {
it( 'posts to the embedding parent origin when one was configured', async () => {
const origins = [];
Expand Down
Loading