Skip to content

Commit

Permalink
Merge pull request #6367 from seadowg/v2024.2.2-3-4
Browse files Browse the repository at this point in the history
Merge changes from v2024.2/3/4
  • Loading branch information
grzesiek2010 authored Aug 23, 2024
2 parents aa10b3b + e40663c commit 34b7970
Show file tree
Hide file tree
Showing 18 changed files with 242 additions and 355 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.odk.collect.androidshared.utils

import timber.log.Timber
import java.io.File

object PathUtils {
@JvmStatic
fun getAbsoluteFilePath(dirPath: String, filePath: String): String {
val absoluteFilePath =
if (filePath.startsWith(dirPath)) filePath else dirPath + File.separator + filePath

val canonicalAbsoluteFilePath = File(absoluteFilePath).canonicalPath
val canonicalDirPath = File(dirPath).canonicalPath
if (!canonicalAbsoluteFilePath.startsWith(canonicalDirPath)) {
Timber.e(
"Attempt to access file outside of Collect directory:\n" +
"dirPath: $dirPath\n" +
"filePath: $filePath\n" +
"absoluteFilePath: $absoluteFilePath\n" +
"canonicalAbsoluteFilePath: $canonicalAbsoluteFilePath\n" +
"canonicalDirPath: $canonicalDirPath"
)
}
return absoluteFilePath
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.odk.collect.androidshared.utils

import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Test
import org.odk.collect.shared.TempFiles
import java.io.File

class PathUtilsTest {
@Test
fun `getAbsoluteFilePath() returns filePath prepended with dirPath`() {
val path = PathUtils.getAbsoluteFilePath("/anotherRoot/anotherDir", "root/dir/file")
assertThat(path, equalTo("/anotherRoot/anotherDir/root/dir/file"))
}

@Test
fun `getAbsoluteFilePath() returns valid path when filePath does not start with seperator`() {
val path = PathUtils.getAbsoluteFilePath("/root/dir", "file")
assertThat(path, equalTo("/root/dir/file"))
}

@Test
fun `getAbsoluteFilePath() returns filePath when it starts with dirPath`() {
val path = PathUtils.getAbsoluteFilePath("/root/dir", "/root/dir/file")
assertThat(path, equalTo("/root/dir/file"))
}

@Test
fun `getAbsoluteFilePath() works when dirPath is not canonical`() {
val tempDir = TempFiles.createTempDir()
val nonCanonicalPath =
tempDir.canonicalPath + File.separator + ".." + File.separator + tempDir.name
assertThat(File(nonCanonicalPath).canonicalPath, equalTo(tempDir.canonicalPath))

val path = PathUtils.getAbsoluteFilePath(nonCanonicalPath, "file")
assertThat(path, equalTo(nonCanonicalPath + File.separator + "file"))
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,68 @@
package org.odk.collect.android.feature.external

import android.content.Context
import android.content.Intent
import android.provider.BaseColumns._ID
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.odk.collect.android.external.InstancesContract
import org.odk.collect.android.instancemanagement.send.InstanceUploaderActivity
import org.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.FormEntryPage
import org.odk.collect.android.support.pages.OkDialog
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain
import org.odk.collect.android.utilities.ApplicationConstants

@RunWith(AndroidJUnit4::class)
class InstanceUploadActionTest {

val collectTestRule = CollectTestRule()
private val rule = CollectTestRule()
private val context = ApplicationProvider.getApplicationContext<Context>()
private val testDependencies = TestDependencies()

@get:Rule
val rule: RuleChain = TestRuleChain.chain()
.around(collectTestRule)
val chain: RuleChain = TestRuleChain.chain(testDependencies)
.around(rule)

@Test
fun whenInstanceDoesNotExist_showsError() {
val instanceIds = longArrayOf(11)
instanceUploadAction(instanceIds)
fun whenIntentIncludesURLExtra_instancesAreUploadedToThatURL() {
rule.startAtMainMenu()
.copyForm("one-question.xml")
.startBlankForm("One Question")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("what is your age", "34"))

OkDialog()
.assertOnPage()
.assertText(org.odk.collect.strings.R.string.no_forms_uploaded)
val instanceId =
context.contentResolver.query(InstancesContract.getUri("DEMO"), null, null, null, null)
.use {
it!!.moveToFirst()
it.getLong(it.getColumnIndex(_ID))
}

val intent = Intent("org.odk.collect.android.INSTANCE_UPLOAD")
intent.type = InstancesContract.CONTENT_TYPE
intent.putExtra(ApplicationConstants.BundleKeys.URL, testDependencies.server.url)
intent.putExtra("instances", longArrayOf(instanceId))

rule.launch(intent, OkDialog())
.assertTextInDialog("One Question - Success")
assertThat(testDependencies.server.submissions.size, equalTo(1))
}

private fun instanceUploadAction(instanceIds: LongArray) {
@Test
fun whenInstanceDoesNotExist_showsError() {
rule.startAtMainMenu()

val intent = Intent("org.odk.collect.android.INSTANCE_UPLOAD")
intent.type = InstancesContract.CONTENT_TYPE
intent.putExtra("instances", instanceIds)
collectTestRule.launch<InstanceUploaderActivity>(intent)
intent.putExtra("instances", longArrayOf(11))

rule.launch(intent, OkDialog())
.assertText(org.odk.collect.strings.R.string.no_forms_uploaded)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ object AnalyticsEvents {

const val INSTANCE_PROVIDER_INSERT = "InstanceProviderInsert"

const val INSTANCE_PROVIDER_UPDATE = "InstanceProviderUpdate"

const val INSTANCE_PROVIDER_DELETE = "InstanceProviderDelete"

/**
Expand All @@ -101,4 +99,9 @@ object AnalyticsEvents {
const val DELETE_SAVED_FORM_FEW = "DeleteSavedFormFew" // < 10
const val DELETE_SAVED_FORM_TENS = "DeleteSavedFormTens" // >= 10
const val DELETE_SAVED_FORM_HUNDREDS = "DeleteSavedFormHundreds" // >= 100

/**
* Tracks how often the INSTANCE_UPLOAD action is used with a custom server URL
*/
const val INSTANCE_UPLOAD_CUSTOM_SERVER = "InstanceUploadCustomServer"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import android.database.Cursor
import android.provider.BaseColumns
import org.odk.collect.android.database.forms.DatabaseFormColumns
import org.odk.collect.android.database.instances.DatabaseInstanceColumns
import org.odk.collect.androidshared.utils.PathUtils.getAbsoluteFilePath
import org.odk.collect.forms.Form
import org.odk.collect.forms.instances.Instance
import org.odk.collect.shared.PathUtils.getAbsoluteFilePath
import org.odk.collect.shared.PathUtils.getRelativeFilePath
import java.lang.Boolean

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.odk.collect.android.database.DatabaseConstants.SAVEPOINTS_DATABASE_VE
import org.odk.collect.android.database.DatabaseConstants.SAVEPOINTS_TABLE_NAME
import org.odk.collect.android.database.savepoints.DatabaseSavepointsColumns.FORM_DB_ID
import org.odk.collect.android.database.savepoints.DatabaseSavepointsColumns.INSTANCE_DB_ID
import org.odk.collect.androidshared.utils.PathUtils.getAbsoluteFilePath
import org.odk.collect.db.sqlite.CursorExt.foldAndClose
import org.odk.collect.db.sqlite.DatabaseConnection
import org.odk.collect.db.sqlite.SQLiteDatabaseExt.delete
Expand Down Expand Up @@ -120,11 +121,11 @@ class DatabaseSavepointsRepository(
return Savepoint(
cursor.getLong(formDbIdColumnIndex),
if (cursor.isNull(instanceDbIdColumnIndex)) null else cursor.getLong(instanceDbIdColumnIndex),
PathUtils.getAbsoluteFilePath(
getAbsoluteFilePath(
cachePath,
cursor.getString(savepointFilePathColumnIndex)
),
PathUtils.getAbsoluteFilePath(
getAbsoluteFilePath(
instancesPath,
cursor.getString(instanceDirPathColumnIndex)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
package org.odk.collect.android.external;

import static android.provider.BaseColumns._ID;
import static org.odk.collect.android.database.DatabaseObjectMapper.getFormFromCurrentCursorPosition;
import static org.odk.collect.android.database.DatabaseObjectMapper.getFormFromValues;
import static org.odk.collect.android.database.DatabaseObjectMapper.getValuesFromForm;
import static org.odk.collect.android.database.forms.DatabaseFormColumns.AUTO_DELETE;
import static org.odk.collect.android.database.forms.DatabaseFormColumns.AUTO_SEND;
import static org.odk.collect.android.database.forms.DatabaseFormColumns.BASE64_RSA_PUBLIC_KEY;
Expand Down Expand Up @@ -52,11 +49,9 @@
import org.odk.collect.android.injection.DaggerUtils;
import org.odk.collect.android.itemsets.FastExternalItemsetsRepository;
import org.odk.collect.android.storage.StoragePathProvider;
import org.odk.collect.android.storage.StorageSubdirectory;
import org.odk.collect.android.utilities.ContentUriHelper;
import org.odk.collect.android.utilities.FormsRepositoryProvider;
import org.odk.collect.android.utilities.InstancesRepositoryProvider;
import org.odk.collect.forms.Form;
import org.odk.collect.forms.FormsRepository;
import org.odk.collect.forms.instances.InstancesRepository;
import org.odk.collect.projects.ProjectsRepository;
Expand Down Expand Up @@ -189,20 +184,7 @@ public String getType(@NonNull Uri uri) {

@Override
public synchronized Uri insert(@NonNull Uri uri, ContentValues initialValues) {
deferDaggerInit();

// Validate the requested uri
if (URI_MATCHER.match(uri) != FORMS) {
throw new IllegalArgumentException("Unknown URI " + uri);
}

String projectId = getProjectId(uri);
logServerEvent(projectId, AnalyticsEvents.FORMS_PROVIDER_INSERT);

String formsPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS, projectId);
String cachePath = storagePathProvider.getOdkDirPath(StorageSubdirectory.CACHE, projectId);
Form form = getFormsRepository(projectId).save(getFormFromValues(initialValues, formsPath, cachePath));
return FormsContract.getUri(projectId, form.getDbId());
return null;
}

/**
Expand Down Expand Up @@ -248,53 +230,7 @@ public int delete(@NonNull Uri uri, String where, String[] whereArgs) {

@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
deferDaggerInit();

String projectId = getProjectId(uri);
logServerEvent(projectId, AnalyticsEvents.FORMS_PROVIDER_UPDATE);

FormsRepository formsRepository = getFormsRepository(projectId);
String formsPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS, projectId);
String cachePath = storagePathProvider.getOdkDirPath(StorageSubdirectory.CACHE, projectId);

int count;

switch (URI_MATCHER.match(uri)) {
case FORMS:
try (Cursor cursor = databaseQuery(projectId, null, where, whereArgs, null, null, null)) {
while (cursor.moveToNext()) {
Form form = getFormFromCurrentCursorPosition(cursor, formsPath, cachePath);
ContentValues existingValues = getValuesFromForm(form, formsPath);
existingValues.putAll(values);

formsRepository.save(getFormFromValues(existingValues, formsPath, cachePath));
}

count = cursor.getCount();
}
break;

case FORM_ID:
Form form = formsRepository.get(ContentUriHelper.getIdFromUri(uri));
if (form != null) {
ContentValues existingValues = getValuesFromForm(form, formsPath);
existingValues.putAll(values);

formsRepository.save(getFormFromValues(existingValues, formsPath, cachePath));
count = 1;
} else {
count = 0;
}

break;

default:
throw new IllegalArgumentException("Unknown URI " + uri);
}

getContext().getContentResolver().notifyChange(uri, null);

return count;
return 0;
}

@NotNull
Expand Down
Loading

0 comments on commit 34b7970

Please sign in to comment.