Skip to content

Commit

Permalink
refactor: replace X-Frame-Options with CSP frame-ancestors (#1059)
Browse files Browse the repository at this point in the history
* refactor: replace X-Frame-Options with CSP frame-ancestors

* feat: remove table headers from copyed html

* fix: clipboard unit tests
  • Loading branch information
boris-w authored Nov 6, 2024
1 parent 15b79dd commit d9e5593
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 27 deletions.
13 changes: 11 additions & 2 deletions apps/nextjs-app/src/features/app/utils/clipboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ vi.mock('@teable/core', () => {
};
});

vi.mock('zod', () => {
return {
z: {
array: () => ({
safeParse: (data: string) => ({ success: true, data }),
}),
},
};
});

describe('clipboard', () => {
const html =
'<meta charset="utf-8"><table data-teable-html-marker="1"><thead><tr><th id="fldziUf9QuQjkbfMuG5" data-field="%7B%22id%22%3A%22fldziUf9QuQjkbfMuG5%22%2C%22name%22%3A%22Name%22%2C%22isPrimary%22%3Atrue%2C%22columnMeta%22%3A%7B%22viwE0sl0GqGdWaBqwFi%22%3A%7B%22order%22%3A0.5%7D%7D%2C%22dbFieldName%22%3A%22Name_fldziUf9QuQjkbfMuG5%22%2C%22dbFieldType%22%3A%22TEXT%22%2C%22type%22%3A%22singleLineText%22%2C%22options%22%3A%7B%7D%2C%22cellValueType%22%3A%22string%22%7D">Name</th><th id="fldpsQvHI4ugP2luizP" data-field="%7B%22id%22%3A%22fldpsQvHI4ugP2luizP%22%2C%22name%22%3A%22Count%22%2C%22columnMeta%22%3A%7B%22viwE0sl0GqGdWaBqwFi%22%3A%7B%22order%22%3A1%7D%7D%2C%22dbFieldName%22%3A%22Count_fldpsQvHI4ugP2luizP%22%2C%22dbFieldType%22%3A%22REAL%22%2C%22type%22%3A%22number%22%2C%22options%22%3A%7B%22formatting%22%3A%7B%22type%22%3A%22decimal%22%2C%22precision%22%3A0%7D%7D%2C%22cellValueType%22%3A%22number%22%7D">Count</th><th id="fldGTKfZvXNXeMJ6nqu" data-field="%7B%22id%22%3A%22fldGTKfZvXNXeMJ6nqu%22%2C%22name%22%3A%22Status%22%2C%22columnMeta%22%3A%7B%22viwE0sl0GqGdWaBqwFi%22%3A%7B%22order%22%3A2%7D%7D%2C%22dbFieldName%22%3A%22Status_fldGTKfZvXNXeMJ6nqu%22%2C%22dbFieldType%22%3A%22TEXT%22%2C%22options%22%3A%7B%22choices%22%3A%5B%7B%22name%22%3A%22light%22%2C%22id%22%3A%22cho2caYhPrI%22%2C%22color%22%3A%22grayBright%22%7D%2C%7B%22name%22%3A%22medium%22%2C%22id%22%3A%22chor2ob8aU7%22%2C%22color%22%3A%22yellowBright%22%7D%2C%7B%22name%22%3A%22heavy%22%2C%22id%22%3A%22choArPr57sO%22%2C%22color%22%3A%22tealBright%22%7D%5D%7D%2C%22type%22%3A%22singleSelect%22%2C%22cellValueType%22%3A%22string%22%7D">Status</th></tr></thead><tbody><tr><td>John</td><td>20</td><td>light</td></tr><tr><td>Tom</td><td>30</td><td>medium</td></tr><tr><td>Bob</td><td>40</td><td>heavy</td></tr></tbody></table>';
const html = `<meta charset="utf-8"><table data-teable-html-marker="1" data-teable-html-header="%5B%7B%22id%22%3A%22fldziUf9QuQjkbfMuG5%22%2C%22name%22%3A%22Name%22%2C%22isPrimary%22%3Atrue%2C%22columnMeta%22%3A%7B%22viwE0sl0GqGdWaBqwFi%22%3A%7B%22order%22%3A0.5%7D%7D%2C%22dbFieldName%22%3A%22Name_fldziUf9QuQjkbfMuG5%22%2C%22dbFieldType%22%3A%22TEXT%22%2C%22type%22%3A%22singleLineText%22%2C%22options%22%3A%7B%7D%2C%22cellValueType%22%3A%22string%22%7D%2C%7B%22id%22%3A%22fldpsQvHI4ugP2luizP%22%2C%22name%22%3A%22Count%22%2C%22columnMeta%22%3A%7B%22viwE0sl0GqGdWaBqwFi%22%3A%7B%22order%22%3A1%7D%7D%2C%22dbFieldName%22%3A%22Count_fldpsQvHI4ugP2luizP%22%2C%22dbFieldType%22%3A%22REAL%22%2C%22type%22%3A%22number%22%2C%22options%22%3A%7B%22formatting%22%3A%7B%22type%22%3A%22decimal%22%2C%22precision%22%3A0%7D%7D%2C%22cellValueType%22%3A%22number%22%7D%2C%7B%22id%22%3A%22fldGTKfZvXNXeMJ6nqu%22%2C%22name%22%3A%22Status%22%2C%22columnMeta%22%3A%7B%22viwE0sl0GqGdWaBqwFi%22%3A%7B%22order%22%3A2%7D%7D%2C%22dbFieldName%22%3A%22Status_fldGTKfZvXNXeMJ6nqu%22%2C%22dbFieldType%22%3A%22TEXT%22%2C%22options%22%3A%7B%22choices%22%3A%5B%7B%22name%22%3A%22light%22%2C%22id%22%3A%22cho2caYhPrI%22%2C%22color%22%3A%22grayBright%22%7D%2C%7B%22name%22%3A%22medium%22%2C%22id%22%3A%22chor2ob8aU7%22%2C%22color%22%3A%22yellowBright%22%7D%2C%7B%22name%22%3A%22heavy%22%2C%22id%22%3A%22choArPr57sO%22%2C%22color%22%3A%22tealBright%22%7D%5D%7D%2C%22type%22%3A%22singleSelect%22%2C%22cellValueType%22%3A%22string%22%7D%5D"><tbody><tr><td>John</td><td>20</td><td>light</td></tr><tr><td>Tom</td><td>30</td><td>medium</td></tr><tr><td>Bob</td><td>40</td><td>heavy</td></tr></tbody></table>`;

const expectedHeader = [
{
Expand Down
36 changes: 13 additions & 23 deletions apps/nextjs-app/src/features/app/utils/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, fieldVoSchema, parseClipboardText, type IFieldVo } from '@teable/core';
import { z } from 'zod';
import { fromZodError } from 'zod-validation-error';

const teableHtmlMarker = 'data-teable-html-marker';
const teableHeader = 'data-teable-html-header';

export const serializerHtml = (data: string, headers: IFieldVo[]) => {
const tableData = parseClipboardText(data);
Expand All @@ -18,13 +20,8 @@ export const serializerHtml = (data: string, headers: IFieldVo[]) => {
.join('')}</tr>`;
})
.join('');
const headerContent = headers
.map((header) => {
return `<th id="${header.id}" data-field="${encodeURIComponent(JSON.stringify(header))}">${header.name}</th>`;
})
.join('');

return `<meta charset="utf-8"><table ${teableHtmlMarker}="1"><thead><tr>${headerContent}</tr></thead><tbody>${bodyContent}</tbody></table>`;
return `<meta charset="utf-8"><table ${teableHtmlMarker}="1" ${teableHeader}="${encodeURIComponent(JSON.stringify(headers))}"><tbody>${bodyContent}</tbody></table>`;
};

export const extractTableHeader = (html?: string) => {
Expand All @@ -34,23 +31,16 @@ export const extractTableHeader = (html?: string) => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const table = doc.querySelector('table');
const headerRow = table?.querySelector('thead tr');
const headerCells = headerRow?.querySelectorAll('th') || [];

const headers = Array.from(headerCells);
let error = '';
const result = headers.map((cell) => {
const fieldVoStr = cell.getAttribute('data-field');
const fieldVo = fieldVoStr ? JSON.parse(decodeURIComponent(fieldVoStr)) : undefined;

const validate = fieldVoSchema.safeParse(fieldVo);
if (validate.success) {
return fieldVo;
}
error = fromZodError(validate.error).message;
return undefined;
}) as IFieldVo[];
return error ? { result: undefined, error } : { result };
const headerStr = table?.getAttribute(teableHeader);
const headers = headerStr ? JSON.parse(decodeURIComponent(headerStr)) : undefined;
if (!headers) {
return { result: undefined };
}
const validate = z.array(fieldVoSchema).safeParse(headers);
if (!validate.success) {
return { result: undefined, error: fromZodError(validate.error).message };
}
return { result: validate.data };
};

export const isTeableHTML = (html: string) => {
Expand Down
4 changes: 2 additions & 2 deletions plugins/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ const nextConfig = {
}
}),
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
key: 'Content-Security-Policy',
value: 'frame-ancestors *'
},
{ key: 'Cross-Origin-Opener-Policy', value: isProd ? 'same-origin' : 'unsafe-none' },
{ key: 'Cross-Origin-Embedder-Policy', value: isProd ? 'same-origin' : 'unsafe-none' }
Expand Down

0 comments on commit d9e5593

Please sign in to comment.