Skip to content

Commit

Permalink
Only accept video/mp4 for story uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
josh-signal committed Aug 12, 2022
1 parent 6da4b03 commit 1d0b1d8
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 2 deletions.
27 changes: 27 additions & 0 deletions ACKNOWLEDGMENTS.md
Expand Up @@ -2127,6 +2127,33 @@ Signal Desktop makes use of the following open source projects.
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

## mp4box

Copyright (c) 2012. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

## mustache

The MIT License
Expand Down
12 changes: 12 additions & 0 deletions _locales/en/messages.json
Expand Up @@ -7527,6 +7527,18 @@
"message": "Can’t download story. You will need to share it again.",
"description": "Description for image errors but when it is your own image"
},
"StoryCreator__error--video-too-long": {
"message": "Cannot post video to story because it is too long",
"description": "Error string for when a video post to story fails"
},
"StoryCreator__error--video-unsupported": {
"message": "Cannot post video to story as it is an unsupported file format",
"description": "Error string for when a video post to story fails"
},
"StoryCreator__error--video-error": {
"message": "Failed to load video",
"description": "Error string for when a video post to story fails"
},
"StoryCreator__text-bg--background": {
"message": "Text has a white background color",
"description": "Button label"
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -127,6 +127,7 @@
"memoizee": "0.4.14",
"mkdirp": "0.5.2",
"moment": "2.29.4",
"mp4box": "0.5.2",
"mustache": "2.3.0",
"node-fetch": "2.6.7",
"node-forge": "1.3.0",
Expand Down
1 change: 1 addition & 0 deletions ts/components/Stories.stories.tsx
Expand Up @@ -48,6 +48,7 @@ export default {
renderStoryViewer: { action: true },
showConversation: { action: true },
showStoriesSettings: { action: true },
showToast: { action: true },
stories: {
defaultValue: [],
},
Expand Down
4 changes: 4 additions & 0 deletions ts/components/Stories.tsx
Expand Up @@ -15,6 +15,7 @@ import type {
import type { LocalizerType } from '../types/Util';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { PropsType as SmartStoryCreatorPropsType } from '../state/smart/StoryCreator';
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
import { MyStories } from './MyStories';
import { StoriesPane } from './StoriesPane';
Expand All @@ -35,6 +36,7 @@ export type PropsType = {
renderStoryCreator: (props: SmartStoryCreatorPropsType) => JSX.Element;
showConversation: ShowConversationType;
showStoriesSettings: () => unknown;
showToast: ShowToastActionCreatorType;
stories: Array<ConversationStoryType>;
toggleHideStories: (conversationId: string) => unknown;
toggleStoriesView: () => unknown;
Expand Down Expand Up @@ -64,6 +66,7 @@ export const Stories = ({
renderStoryCreator,
showConversation,
showStoriesSettings,
showToast,
stories,
toggleHideStories,
toggleStoriesView,
Expand Down Expand Up @@ -118,6 +121,7 @@ export const Stories = ({
onStoriesSettings={showStoriesSettings}
queueStoryDownload={queueStoryDownload}
showConversation={showConversation}
showToast={showToast}
stories={stories}
toggleHideStories={toggleHideStories}
toggleStoriesView={toggleStoriesView}
Expand Down
32 changes: 30 additions & 2 deletions ts/components/StoriesPane.tsx
Expand Up @@ -16,12 +16,18 @@ import type {
} from '../types/Stories';
import type { LocalizerType } from '../types/Util';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
import { ContextMenu } from './ContextMenu';
import { MyStoriesButton } from './MyStoriesButton';
import { SearchInput } from './SearchInput';
import { StoryListItem } from './StoryListItem';
import { Theme } from '../util/theme';
import { ToastType } from '../state/ducks/toast';
import { isNotNil } from '../util/isNotNil';
import {
isVideoGoodForStories,
ReasonVideoNotGood,
} from '../util/isVideoGoodForStories';

const FUSE_OPTIONS: Fuse.IFuseOptions<ConversationStoryType> = {
getFn: (story, path) => {
Expand Down Expand Up @@ -70,6 +76,7 @@ export type PropsType = {
onStoriesSettings: () => unknown;
queueStoryDownload: (storyId: string) => unknown;
showConversation: ShowConversationType;
showToast: ShowToastActionCreatorType;
stories: Array<ConversationStoryType>;
toggleHideStories: (conversationId: string) => unknown;
toggleStoriesView: () => unknown;
Expand All @@ -87,6 +94,7 @@ export const StoriesPane = ({
onStoriesSettings,
queueStoryDownload,
showConversation,
showToast,
stories,
toggleHideStories,
toggleStoriesView,
Expand Down Expand Up @@ -125,15 +133,35 @@ export const StoriesPane = ({
label: i18n('Stories__add-story--media'),
onClick: () => {
const input = document.createElement('input');
input.accept = 'image/*,video/*';
input.accept = 'image/*,video/mp4';
input.type = 'file';
input.onchange = () => {
input.onchange = async () => {
const file = input.files ? input.files[0] : undefined;

if (!file) {
return;
}

const result = await isVideoGoodForStories(file);

if (
result === ReasonVideoNotGood.UnsupportedCodec ||
result === ReasonVideoNotGood.UnsupportedContainer
) {
showToast(ToastType.StoryVideoUnsupported);
return;
}

if (result === ReasonVideoNotGood.TooLong) {
showToast(ToastType.StoryVideoTooLong);
return;
}

if (result !== ReasonVideoNotGood.AllGoodNevermind) {
showToast(ToastType.StoryVideoError);
return;
}

onAddStory(file);
};
input.click();
Expand Down
15 changes: 15 additions & 0 deletions ts/components/ToastManager.stories.tsx
Expand Up @@ -50,3 +50,18 @@ export const MessageBodyTooLong = Template.bind({});
MessageBodyTooLong.args = {
toastType: ToastType.MessageBodyTooLong,
};

export const StoryVideoTooLong = Template.bind({});
StoryVideoTooLong.args = {
toastType: ToastType.StoryVideoTooLong,
};

export const StoryVideoUnsupported = Template.bind({});
StoryVideoUnsupported.args = {
toastType: ToastType.StoryVideoUnsupported,
};

export const StoryVideoError = Template.bind({});
StoryVideoError.args = {
toastType: ToastType.StoryVideoError,
};
24 changes: 24 additions & 0 deletions ts/components/ToastManager.tsx
Expand Up @@ -63,6 +63,30 @@ export const ToastManager = ({
);
}

if (toastType === ToastType.StoryVideoTooLong) {
return (
<Toast onClose={hideToast}>
{i18n('StoryCreator__error--video-too-long')}
</Toast>
);
}

if (toastType === ToastType.StoryVideoUnsupported) {
return (
<Toast onClose={hideToast}>
{i18n('StoryCreator__error--video-unsupported')}
</Toast>
);
}

if (toastType === ToastType.StoryVideoError) {
return (
<Toast onClose={hideToast}>
{i18n('StoryCreator__error--video-error')}
</Toast>
);
}

strictAssert(
toastType === undefined,
`Unhandled toast of type: ${toastType}`
Expand Down
68 changes: 68 additions & 0 deletions ts/mp4box.d.ts
@@ -0,0 +1,68 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

declare module 'mp4box' {
interface MP4MediaTrack {
alternate_group: number;
bitrate: number;
codec: string;
created: Date;
duration: number;
id: number;
language: string;
layer: number;
modified: Date;
movie_duration: number;
nb_samples: number;
timescale: number;
track_height: number;
track_width: number;
volume: number;
}

interface MP4VideoData {
height: number;
width: number;
}

interface MP4VideoTrack extends MP4MediaTrack {
video: MP4VideoData;
}

interface MP4AudioData {
channel_count: number;
sample_rate: number;
sample_size: number;
}

interface MP4AudioTrack extends MP4MediaTrack {
audio: MP4AudioData;
}

type MP4Track = MP4VideoTrack | MP4AudioTrack;

interface MP4Info {
brands: Array<string>;
created: Date;
duration: number;
fragment_duration: number;
hasIOD: boolean;
isFragmented: boolean;
isProgressive: boolean;
mime: string;
modified: Date;
timescale: number;
tracks: Array<MP4Track>;
}

export type MP4ArrayBuffer = ArrayBuffer & { fileStart: number };

export interface MP4File {
appendBuffer(data: MP4ArrayBuffer): number;
flush(): void;
onError?: (e: string) => void;
onReady?: (info: MP4Info) => void;
}

export function createFile(): MP4File;
}
3 changes: 3 additions & 0 deletions ts/state/ducks/toast.ts
Expand Up @@ -9,6 +9,9 @@ export enum ToastType {
StoryMuted = 'StoryMuted',
StoryReact = 'StoryReact',
StoryReply = 'StoryReply',
StoryVideoError = 'StoryVideoError',
StoryVideoTooLong = 'StoryVideoTooLong',
StoryVideoUnsupported = 'StoryVideoUnsupported',
}

// State
Expand Down
3 changes: 3 additions & 0 deletions ts/state/smart/Stories.tsx
Expand Up @@ -18,6 +18,7 @@ import { saveAttachment } from '../../util/saveAttachment';
import { useConversationsActions } from '../ducks/conversations';
import { useGlobalModalActions } from '../ducks/globalModals';
import { useStoriesActions } from '../ducks/stories';
import { useToastActions } from '../ducks/toast';

function renderStoryCreator({
file,
Expand All @@ -31,6 +32,7 @@ export function SmartStories(): JSX.Element | null {
const { showConversation, toggleHideStories } = useConversationsActions();
const { showStoriesSettings, toggleForwardMessageModal } =
useGlobalModalActions();
const { showToast } = useToastActions();

const i18n = useSelector<StateType, LocalizerType>(getIntl);

Expand Down Expand Up @@ -70,6 +72,7 @@ export function SmartStories(): JSX.Element | null {
renderStoryCreator={renderStoryCreator}
showConversation={showConversation}
showStoriesSettings={showStoriesSettings}
showToast={showToast}
stories={stories}
toggleHideStories={toggleHideStories}
{...storiesActions}
Expand Down

0 comments on commit 1d0b1d8

Please sign in to comment.