Skip to content

Commit b899e66

Browse files
committed
Verify file name validity during user input.
The automated test only tests this partially, because the only invalid name was an emtpy name. All other characters which are typically unsuitable for a filename, like the directory separator '/' are not properly rejected during the test run, because the FileNameValidator is based on the user capabilities, which are null because this is a sever-less test. Signed-off-by: Philipp Hasper <vcs@hasper.info>
1 parent ac1fece commit b899e66

4 files changed

Lines changed: 94 additions & 2 deletions

File tree

app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.owncloud.android.AbstractIT
3030
import com.owncloud.android.R
3131
import com.owncloud.android.datamodel.OCFile
3232
import com.owncloud.android.utils.ScreenshotTest
33+
import org.hamcrest.Matchers.not
3334
import org.junit.Rule
3435
import org.junit.Test
3536
import org.junit.rules.TestRule
@@ -74,7 +75,7 @@ class ReceiveExternalFilesActivityIT : AbstractIT() {
7475
val imageFile = getDummyFile("image.jpg")
7576
val intent = createSendIntent(imageFile)
7677

77-
// Create folders with the necessary permissions
78+
// Create folders with the necessary permissions and another test file
7879
val mainFolder = OCFile("/folder/").apply {
7980
permissions = OCFile.PERMISSION_CAN_CREATE_FILE_AND_FOLDER
8081
setFolder()
@@ -85,6 +86,9 @@ class ReceiveExternalFilesActivityIT : AbstractIT() {
8586
setFolder()
8687
fileDataStorageManager.saveNewFile(this)
8788
}
89+
val otherFile = OCFile("${mainFolder.remotePath}Other Image File.jpg").apply {
90+
fileDataStorageManager.saveNewFile(this)
91+
}
8892

8993
// Store the folder in preferences, so the activity starts from there.
9094
@Suppress("DEPRECATION")
@@ -116,6 +120,9 @@ class ReceiveExternalFilesActivityIT : AbstractIT() {
116120
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_TAB))
117121
.perform(ViewActions.click())
118122
.check(matches(withSelectedText(secondFileName.removeFileExtension())))
123+
onView(withText(R.string.uploader_btn_upload_text))
124+
.check(matches(isDisplayed()))
125+
.check(matches(isEnabled()))
119126

120127
// Set a file name without file extension
121128
val thirdFileName = "No extension"
@@ -127,6 +134,35 @@ class ReceiveExternalFilesActivityIT : AbstractIT() {
127134
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_TAB))
128135
.perform(ViewActions.click())
129136
.check(matches(withSelectedText(thirdFileName)))
137+
onView(withText(R.string.uploader_btn_upload_text))
138+
.check(matches(isDisplayed()))
139+
.check(matches(isEnabled()))
140+
141+
// Test an invalid filename. Note: as the user is null, the capabilities are also null, so the name checker
142+
// will not reject any special characters like '/'. So we only test empty and an existing file name
143+
onView(withId(R.id.user_input))
144+
.perform(ViewActions.clearText())
145+
.check(matches(withText("")))
146+
onView(withText(R.string.uploader_btn_upload_text))
147+
.check(matches(isDisplayed()))
148+
.check(matches(not(isEnabled())))
149+
onView(withId(R.id.user_input))
150+
.perform(ViewActions.click())
151+
.perform(ViewActions.typeTextIntoFocusedView(otherFile.fileName))
152+
.check(matches(withText(otherFile.fileName)))
153+
onView(withText(R.string.uploader_btn_upload_text))
154+
.check(matches(isDisplayed()))
155+
.check(matches(not(isEnabled())))
156+
157+
val fourthFileName = "New file name.jpg"
158+
onView(withId(R.id.user_input))
159+
.perform(ViewActions.click())
160+
.perform(ViewActions.clearText())
161+
.perform(ViewActions.typeTextIntoFocusedView(fourthFileName))
162+
.check(matches(withText(fourthFileName)))
163+
onView(withText(R.string.uploader_btn_upload_text))
164+
.check(matches(isDisplayed()))
165+
.check(matches(isEnabled()))
130166
}
131167
}
132168
}

app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
import android.os.Handler;
3131
import android.os.Looper;
3232
import android.os.Parcelable;
33+
import android.text.Editable;
3334
import android.text.TextUtils;
35+
import android.text.TextWatcher;
3436
import android.text.format.DateFormat;
3537
import android.view.LayoutInflater;
3638
import android.view.Menu;
@@ -89,6 +91,8 @@
8991
import com.owncloud.android.utils.MimeType;
9092
import com.owncloud.android.utils.theme.ViewThemeUtils;
9193

94+
import org.apache.commons.io.FilenameUtils;
95+
9296
import java.io.File;
9397
import java.io.FileWriter;
9498
import java.io.IOException;
@@ -129,7 +133,7 @@
129133
public class ReceiveExternalFilesActivity extends FileActivity
130134
implements View.OnClickListener, CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener,
131135
SortingOrderDialogFragment.OnSortingOrderListener, Injectable, AccountChooserInterface,
132-
ReceiveExternalFilesAdapter.OnItemClickListener {
136+
ReceiveExternalFilesAdapter.OnItemClickListener, TextWatcher {
133137

134138
private static final String TAG = ReceiveExternalFilesActivity.class.getSimpleName();
135139

@@ -330,6 +334,53 @@ public void selectFile(OCFile file) {
330334
}
331335
}
332336

337+
@Override
338+
public void afterTextChanged(Editable editable) {}
339+
340+
@Override
341+
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
342+
343+
// TODO: this is copy-paste from RenameFileDialogFragment.kt
344+
@Override
345+
public void onTextChanged(CharSequence s, int start, int before, int count) {
346+
final var newFileName = Objects.requireNonNullElse(binding.userInput.getText(), "").toString();
347+
final var positiveButton = binding.uploaderChooseFolder;
348+
349+
final var existingFiles = receiveExternalFilesAdapter.getFileNames();
350+
final var errorMessage = FileNameValidator.INSTANCE.checkFileName(newFileName, getCapabilities(), this, existingFiles);
351+
352+
if (FileNameValidator.INSTANCE.isFileHidden(newFileName)) {
353+
binding.userInputContainer.setError(getText(R.string.hidden_file_name_warning));
354+
positiveButton.setEnabled(true);
355+
} else if (errorMessage != null) {
356+
binding.userInputContainer.setError(errorMessage);
357+
positiveButton.setEnabled(false);
358+
} else if (checkExtensionRenamed(newFileName)) {
359+
binding.userInputContainer.setError(getText(R.string.warn_rename_extension));
360+
positiveButton.setEnabled(true);
361+
} else if (binding.userInputContainer.getError() != null) {
362+
binding.userInputContainer.setError(null);
363+
// Called to remove extra padding
364+
binding.userInputContainer.setErrorEnabled(false);
365+
positiveButton.setEnabled(true);
366+
}
367+
}
368+
369+
private boolean checkExtensionRenamed(@NonNull String newFileName) {
370+
if (mStreamsToUpload == null || mStreamsToUpload.size() != 1) {
371+
return false;
372+
}
373+
final String previousFileName = getDisplayNameForUri((Uri) mStreamsToUpload.get(0), getActivity());
374+
if (previousFileName == null) {
375+
return false;
376+
}
377+
378+
var previousExtension = FilenameUtils.getExtension(previousFileName);
379+
var newExtension = FilenameUtils.getExtension(newFileName);
380+
381+
return !Objects.equals(previousExtension, newExtension);
382+
}
383+
333384
public static class DialogNoAccount extends DialogFragment {
334385
private final ViewThemeUtils viewThemeUtils;
335386

@@ -860,6 +911,7 @@ private void setupFileNameInputField() {
860911

861912
binding.userInput.setVisibility(View.VISIBLE);
862913
binding.userInput.setText(fileName);
914+
binding.userInput.addTextChangedListener(this);
863915
mFileDisplayNameTransformer = uri ->
864916
Objects.requireNonNullElse(binding.userInput.getText(), fileName).toString();
865917

app/src/main/java/com/owncloud/android/ui/adapter/ReceiveExternalFilesAdapter.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager.ThumbnailGeneration
2626
import com.owncloud.android.utils.DisplayUtils
2727
import com.owncloud.android.utils.MimeTypeUtil
2828
import com.owncloud.android.utils.theme.ViewThemeUtils
29+
import java.util.Objects
2930

3031
@Suppress("LongParameterList")
3132
class ReceiveExternalFilesAdapter(
@@ -159,4 +160,6 @@ class ReceiveExternalFilesAdapter(
159160
}
160161

161162
override fun getItemCount() = filteredFiles.size
163+
164+
fun getFileNames(): Set<String> = files.map { it.fileName }.toSet()
162165
}

app/src/main/res/layout/receive_external_files.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
android:background="@color/list_divider_background" />
4949

5050
<com.google.android.material.textfield.TextInputLayout
51+
android:id="@+id/user_input_container"
5152
android:layout_width="match_parent"
5253
android:layout_height="wrap_content"
5354
android:hint="@string/hint_name"

0 commit comments

Comments
 (0)