[FIX] Unify upload check (#1229)

This commit is contained in:
Diego Mello 2019-09-24 17:16:59 -03:00 committed by GitHub
parent ae7a9cba60
commit 319ca7f044
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 155 deletions

View File

@ -5,6 +5,7 @@ import {
} from 'react-native'; } from 'react-native';
import { AudioRecorder, AudioUtils } from 'react-native-audio'; import { AudioRecorder, AudioUtils } from 'react-native-audio';
import { BorderlessButton } from 'react-native-gesture-handler'; import { BorderlessButton } from 'react-native-gesture-handler';
import FileSystem from 'expo-file-system';
import styles from './styles'; import styles from './styles';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -68,7 +69,7 @@ export default class extends React.PureComponent {
// //
AudioRecorder.onFinished = (data) => { AudioRecorder.onFinished = (data) => {
if (!this.recordingCanceled && isIOS) { if (!this.recordingCanceled && isIOS) {
this.finishRecording(data.status === 'OK', data.audioFileURL); this.finishRecording(data.status === 'OK', data.audioFileURL, data.audioFileSize);
} }
}; };
AudioRecorder.startRecording(); AudioRecorder.startRecording();
@ -80,7 +81,7 @@ export default class extends React.PureComponent {
} }
} }
finishRecording = (didSucceed, filePath) => { finishRecording = (didSucceed, filePath, size) => {
const { onFinish } = this.props; const { onFinish } = this.props;
if (!didSucceed) { if (!didSucceed) {
return onFinish && onFinish(didSucceed); return onFinish && onFinish(didSucceed);
@ -90,9 +91,11 @@ export default class extends React.PureComponent {
} }
const fileInfo = { const fileInfo = {
name: this.name, name: this.name,
mime: 'audio/aac',
type: 'audio/aac', type: 'audio/aac',
store: 'Uploads', store: 'Uploads',
path: filePath path: filePath,
size
}; };
return onFinish && onFinish(fileInfo); return onFinish && onFinish(fileInfo);
} }
@ -102,7 +105,8 @@ export default class extends React.PureComponent {
this.recording = false; this.recording = false;
const filePath = await AudioRecorder.stopRecording(); const filePath = await AudioRecorder.stopRecording();
if (isAndroid) { if (isAndroid) {
this.finishRecording(true, filePath); const data = await FileSystem.getInfoAsync(decodeURIComponent(filePath));
this.finishRecording(true, filePath, data.size);
} }
} catch (err) { } catch (err) {
this.finishRecording(false); this.finishRecording(false);

View File

@ -2,7 +2,6 @@ import React, { Component } from 'react';
import { import {
View, Text, StyleSheet, Image, ScrollView, TouchableHighlight View, Text, StyleSheet, Image, ScrollView, TouchableHighlight
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
import { responsive } from 'react-native-responsive-ui'; import { responsive } from 'react-native-responsive-ui';
@ -13,9 +12,8 @@ import Button from '../Button';
import I18n from '../../i18n'; import I18n from '../../i18n';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { isIOS } from '../../utils/deviceInfo'; import { isIOS } from '../../utils/deviceInfo';
import { canUploadFile } from '../../utils/media';
import { import {
COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_DANGER COLOR_PRIMARY, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE
} from '../../constants/colors'; } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
@ -75,23 +73,6 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
textAlign: 'center' textAlign: 'center'
}, },
errorIcon: {
color: COLOR_DANGER
},
fileMime: {
...sharedStyles.textColorTitle,
...sharedStyles.textBold,
textAlign: 'center',
fontSize: 20,
marginBottom: 20
},
errorContainer: {
margin: 20,
flex: 1,
textAlign: 'center',
justifyContent: 'center',
alignItems: 'center'
},
video: { video: {
flex: 1, flex: 1,
borderRadius: 4, borderRadius: 4,
@ -110,9 +91,7 @@ class UploadModal extends Component {
file: PropTypes.object, file: PropTypes.object,
close: PropTypes.func, close: PropTypes.func,
submit: PropTypes.func, submit: PropTypes.func,
window: PropTypes.object, window: PropTypes.object
FileUpload_MediaTypeWhiteList: PropTypes.string,
FileUpload_MaxFileSize: PropTypes.number
} }
state = { state = {
@ -154,79 +133,12 @@ class UploadModal extends Component {
return false; return false;
} }
canUploadFile = () => {
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize, file } = this.props;
if (!(file && file.path)) {
return true;
}
if (file.size > FileUpload_MaxFileSize) {
return false;
}
// if white list is empty, all media types are enabled
if (!FileUpload_MediaTypeWhiteList) {
return true;
}
const allowedMime = FileUpload_MediaTypeWhiteList.split(',');
if (allowedMime.includes(file.mime)) {
return true;
}
const wildCardGlob = '/*';
const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0);
if (wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) {
return true;
}
return false;
}
submit = () => { submit = () => {
const { file, submit } = this.props; const { file, submit } = this.props;
const { name, description } = this.state; const { name, description } = this.state;
submit({ ...file, name, description }); submit({ ...file, name, description });
} }
renderError = () => {
const { file, FileUpload_MaxFileSize, close } = this.props;
const { window: { width } } = this.props;
const errorMessage = (FileUpload_MaxFileSize < file.size)
? 'error-file-too-large'
: 'error-invalid-file-type';
return (
<View style={[styles.container, { width: width - 32 }]}>
<View style={styles.titleContainer}>
<Text style={styles.title}>{I18n.t(errorMessage)}</Text>
</View>
<View style={styles.errorContainer}>
<CustomIcon name='circle-cross' size={120} style={styles.errorIcon} />
</View>
<Text style={styles.fileMime}>{ file.mime }</Text>
<View style={styles.buttonContainer}>
{
(isIOS)
? (
<Button
title={I18n.t('Cancel')}
type='secondary'
backgroundColor={cancelButtonColor}
style={styles.button}
onPress={close}
/>
)
: (
<TouchableHighlight
onPress={close}
style={[styles.androidButton, { backgroundColor: cancelButtonColor }]}
underlayColor={cancelButtonColor}
activeOpacity={0.5}
>
<Text style={[styles.androidButtonText, { ...sharedStyles.textBold, color: COLOR_PRIMARY }]}>{I18n.t('Cancel')}</Text>
</TouchableHighlight>
)
}
</View>
</View>
);
}
renderButtons = () => { renderButtons = () => {
const { close } = this.props; const { close } = this.props;
if (isIOS) { if (isIOS) {
@ -288,10 +200,9 @@ class UploadModal extends Component {
render() { render() {
const { const {
window: { width }, isVisible, close, file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize window: { width }, isVisible, close
} = this.props; } = this.props;
const { name, description } = this.state; const { name, description } = this.state;
const showError = !canUploadFile(file, { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize });
return ( return (
<Modal <Modal
isVisible={isVisible} isVisible={isVisible}
@ -304,37 +215,29 @@ class UploadModal extends Component {
hideModalContentWhileAnimating hideModalContentWhileAnimating
avoidKeyboard avoidKeyboard
> >
{(showError) ? this.renderError() <View style={[styles.container, { width: width - 32 }]}>
: ( <View style={styles.titleContainer}>
<View style={[styles.container, { width: width - 32 }]}> <Text style={styles.title}>{I18n.t('Upload_file_question_mark')}</Text>
<View style={styles.titleContainer}> </View>
<Text style={styles.title}>{I18n.t('Upload_file_question_mark')}</Text>
</View>
<ScrollView style={styles.scrollView}> <ScrollView style={styles.scrollView}>
{this.renderPreview()} {this.renderPreview()}
<TextInput <TextInput
placeholder={I18n.t('File_name')} placeholder={I18n.t('File_name')}
value={name} value={name}
onChangeText={value => this.setState({ name: value })} onChangeText={value => this.setState({ name: value })}
/> />
<TextInput <TextInput
placeholder={I18n.t('File_description')} placeholder={I18n.t('File_description')}
value={description} value={description}
onChangeText={value => this.setState({ description: value })} onChangeText={value => this.setState({ description: value })}
/> />
</ScrollView> </ScrollView>
{this.renderButtons()} {this.renderButtons()}
</View> </View>
)}
</Modal> </Modal>
); );
} }
} }
const mapStateToProps = state => ({ export default responsive(UploadModal);
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize
});
export default responsive(connect(mapStateToProps)(UploadModal));

View File

@ -30,6 +30,7 @@ import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import { isAndroid } from '../../utils/deviceInfo'; import { isAndroid } from '../../utils/deviceInfo';
import CommandPreview from './CommandPreview'; import CommandPreview from './CommandPreview';
import { canUploadFile } from '../../utils/media';
const MENTIONS_TRACKING_TYPE_USERS = '@'; const MENTIONS_TRACKING_TYPE_USERS = '@';
const MENTIONS_TRACKING_TYPE_EMOJIS = ':'; const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
@ -73,6 +74,8 @@ class MessageBox extends Component {
roomType: PropTypes.string, roomType: PropTypes.string,
tmid: PropTypes.string, tmid: PropTypes.string,
replyWithMention: PropTypes.bool, replyWithMention: PropTypes.bool,
FileUpload_MediaTypeWhiteList: PropTypes.string,
FileUpload_MaxFileSize: PropTypes.number,
getCustomEmoji: PropTypes.func, getCustomEmoji: PropTypes.func,
editCancel: PropTypes.func.isRequired, editCancel: PropTypes.func.isRequired,
editRequest: PropTypes.func.isRequired, editRequest: PropTypes.func.isRequired,
@ -435,6 +438,16 @@ class MessageBox extends Component {
this.setShowSend(false); this.setShowSend(false);
} }
canUploadFile = (file) => {
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props;
const result = canUploadFile(file, { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize });
if (result.success) {
return true;
}
Alert.alert(I18n.t('Error_uploading'), I18n.t(result.error));
return false;
}
sendMediaMessage = async(file) => { sendMediaMessage = async(file) => {
const { const {
rid, tmid, baseUrl: server, user rid, tmid, baseUrl: server, user
@ -458,7 +471,9 @@ class MessageBox extends Component {
takePhoto = async() => { takePhoto = async() => {
try { try {
const image = await ImagePicker.openCamera(this.imagePickerConfig); const image = await ImagePicker.openCamera(this.imagePickerConfig);
this.showUploadModal(image); if (this.canUploadFile(image)) {
this.showUploadModal(image);
}
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -467,7 +482,9 @@ class MessageBox extends Component {
takeVideo = async() => { takeVideo = async() => {
try { try {
const video = await ImagePicker.openCamera(this.videoPickerConfig); const video = await ImagePicker.openCamera(this.videoPickerConfig);
this.showUploadModal(video); if (this.canUploadFile(video)) {
this.showUploadModal(video);
}
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -476,7 +493,9 @@ class MessageBox extends Component {
chooseFromLibrary = async() => { chooseFromLibrary = async() => {
try { try {
const image = await ImagePicker.openPicker(this.libraryPickerConfig); const image = await ImagePicker.openPicker(this.libraryPickerConfig);
this.showUploadModal(image); if (this.canUploadFile(image)) {
this.showUploadModal(image);
}
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -487,12 +506,15 @@ class MessageBox extends Component {
const res = await DocumentPicker.pick({ const res = await DocumentPicker.pick({
type: [DocumentPicker.types.allFiles] type: [DocumentPicker.types.allFiles]
}); });
this.showUploadModal({ const file = {
filename: res.name, filename: res.name,
size: res.size, size: res.size,
mime: res.type, mime: res.type,
path: res.uri path: res.uri
}); };
if (this.canUploadFile(file)) {
this.showUploadModal(file);
}
} catch (e) { } catch (e) {
if (!DocumentPicker.isCancel(e)) { if (!DocumentPicker.isCancel(e)) {
log(e); log(e);
@ -560,11 +582,10 @@ class MessageBox extends Component {
}); });
if (fileInfo) { if (fileInfo) {
try { try {
await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user); if (this.canUploadFile(fileInfo)) {
} catch (e) { await RocketChat.sendFileMessage(rid, fileInfo, tmid, server, user);
if (e && e.error === 'error-file-too-large') {
return Alert.alert(I18n.t(e.error));
} }
} catch (e) {
log(e); log(e);
} }
} }
@ -921,7 +942,9 @@ const mapStateToProps = state => ({
id: state.login.user && state.login.user.id, id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username, username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token token: state.login.user && state.login.user.token
} },
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize
}); });
const dispatchToProps = ({ const dispatchToProps = ({

View File

@ -30,14 +30,9 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers'); const serversCollection = serversDB.collections.get('servers');
const serverInfo = await serversCollection.find(server); const serverInfo = await serversCollection.find(server);
const { FileUpload_MaxFileSize, id: Site_Url } = serverInfo; const { id: Site_Url } = serverInfo;
const { id, token } = user; const { id, token } = user;
// -1 maxFileSize means there is no limit
if (FileUpload_MaxFileSize > -1 && fileInfo.size > FileUpload_MaxFileSize) {
return reject({ error: 'error-file-too-large' }); // eslint-disable-line
}
const uploadUrl = `${ Site_Url }/api/v1/rooms.upload/${ rid }`; const uploadUrl = `${ Site_Url }/api/v1/rooms.upload/${ rid }`;
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();

View File

@ -1,23 +1,23 @@
export const canUploadFile = (file, serverInfo) => { export const canUploadFile = (file, serverInfo) => {
const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = serverInfo; const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = serverInfo;
if (!(file && file.path)) { if (!(file && file.path)) {
return true; return { success: true };
} }
if (file.size > FileUpload_MaxFileSize) { if (FileUpload_MaxFileSize > -1 && file.size > FileUpload_MaxFileSize) {
return false; return { success: false, error: 'error-file-too-large' };
} }
// if white list is empty, all media types are enabled // if white list is empty, all media types are enabled
if (!FileUpload_MediaTypeWhiteList) { if (!FileUpload_MediaTypeWhiteList) {
return true; return { success: true };
} }
const allowedMime = FileUpload_MediaTypeWhiteList.split(','); const allowedMime = FileUpload_MediaTypeWhiteList.split(',');
if (allowedMime.includes(file.mime)) { if (allowedMime.includes(file.mime)) {
return true; return { success: true };
} }
const wildCardGlob = '/*'; const wildCardGlob = '/*';
const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0); const wildCards = allowedMime.filter(item => item.indexOf(wildCardGlob) > 0);
if (wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) { if (file.mime && wildCards.includes(file.mime.replace(/(\/.*)$/, wildCardGlob))) {
return true; return { success: true };
} }
return false; return { success: false, error: 'error-invalid-file-type' };
}; };

View File

@ -199,12 +199,14 @@ class ShareListView extends React.Component {
this.servers = await serversCollection.query().fetch(); this.servers = await serversCollection.query().fetch();
this.chats = this.data.slice(0, LIMIT); this.chats = this.data.slice(0, LIMIT);
const serverInfo = await serversCollection.find(server); const serverInfo = await serversCollection.find(server);
const canUploadFileResult = canUploadFile(fileInfo || fileData, serverInfo);
this.internalSetState({ this.internalSetState({
chats: this.chats ? this.chats.slice() : [], chats: this.chats ? this.chats.slice() : [],
servers: this.servers ? this.servers.slice() : [], servers: this.servers ? this.servers.slice() : [],
loading: false, loading: false,
showError: !canUploadFile(fileInfo || fileData, serverInfo), showError: !canUploadFileResult.success,
error: canUploadFileResult.error,
serverInfo serverInfo
}); });
this.forceUpdate(); this.forceUpdate();
@ -378,12 +380,8 @@ class ShareListView extends React.Component {
renderError = () => { renderError = () => {
const { const {
fileInfo: file, loading, searching, serverInfo fileInfo: file, loading, searching, error
} = this.state; } = this.state;
const { FileUpload_MaxFileSize } = serverInfo;
const errorMessage = (FileUpload_MaxFileSize < file.size)
? 'error-file-too-large'
: 'error-invalid-file-type';
if (loading) { if (loading) {
return <ActivityIndicator style={styles.loading} />; return <ActivityIndicator style={styles.loading} />;
@ -400,7 +398,7 @@ class ShareListView extends React.Component {
: null : null
} }
<View style={[styles.container, styles.centered]}> <View style={[styles.container, styles.centered]}>
<Text style={styles.title}>{I18n.t(errorMessage)}</Text> <Text style={styles.title}>{I18n.t(error)}</Text>
<CustomIcon name='circle-cross' size={120} style={styles.errorIcon} /> <CustomIcon name='circle-cross' size={120} style={styles.errorIcon} />
<Text style={styles.fileMime}>{ file.mime }</Text> <Text style={styles.fileMime}>{ file.mime }</Text>
</View> </View>