FileContentProvider.java 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author Bartek Przybylski
  5. * @author David A. Velasco
  6. * @author masensio
  7. * Copyright (C) 2011 Bartek Przybylski
  8. * Copyright (C) 2016 ownCloud Inc.
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License version 2,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. package com.owncloud.android.providers;
  23. import android.content.ContentProvider;
  24. import android.content.ContentProviderOperation;
  25. import android.content.ContentProviderResult;
  26. import android.content.ContentUris;
  27. import android.content.ContentValues;
  28. import android.content.Context;
  29. import android.content.OperationApplicationException;
  30. import android.content.UriMatcher;
  31. import android.database.Cursor;
  32. import android.database.SQLException;
  33. import android.database.sqlite.SQLiteDatabase;
  34. import android.net.Uri;
  35. import android.os.Binder;
  36. import android.text.TextUtils;
  37. import com.nextcloud.client.core.Clock;
  38. import com.nextcloud.client.database.NextcloudDatabase;
  39. import com.owncloud.android.R;
  40. import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
  41. import com.owncloud.android.lib.common.utils.Log_OC;
  42. import com.owncloud.android.lib.resources.shares.ShareType;
  43. import com.owncloud.android.utils.MimeType;
  44. import java.util.ArrayList;
  45. import java.util.Locale;
  46. import javax.inject.Inject;
  47. import androidx.annotation.NonNull;
  48. import androidx.annotation.Nullable;
  49. import androidx.annotation.VisibleForTesting;
  50. import androidx.sqlite.db.SupportSQLiteDatabase;
  51. import androidx.sqlite.db.SupportSQLiteOpenHelper;
  52. import androidx.sqlite.db.SupportSQLiteQuery;
  53. import androidx.sqlite.db.SupportSQLiteQueryBuilder;
  54. import dagger.android.AndroidInjection;
  55. import third_parties.aosp.SQLiteTokenizer;
  56. /**
  57. * The ContentProvider for the ownCloud App.
  58. */
  59. @SuppressWarnings("PMD.AvoidDuplicateLiterals")
  60. public class FileContentProvider extends ContentProvider {
  61. private static final int SINGLE_FILE = 1;
  62. private static final int DIRECTORY = 2;
  63. private static final int ROOT_DIRECTORY = 3;
  64. private static final int SHARES = 4;
  65. private static final int CAPABILITIES = 5;
  66. private static final int UPLOADS = 6;
  67. private static final int SYNCED_FOLDERS = 7;
  68. private static final int EXTERNAL_LINKS = 8;
  69. private static final int VIRTUAL = 10;
  70. private static final int FILESYSTEM = 11;
  71. private static final String TAG = FileContentProvider.class.getSimpleName();
  72. // todo avoid string concatenation and use string formatting instead later.
  73. private static final String ERROR = "ERROR ";
  74. private static final int SINGLE_PATH_SEGMENT = 1;
  75. public static final int MINIMUM_PATH_SEGMENTS_SIZE = 1;
  76. private static final String[] PROJECTION_CONTENT_TYPE = new String[]{
  77. ProviderTableMeta._ID, ProviderTableMeta.FILE_CONTENT_TYPE
  78. };
  79. private static final String[] PROJECTION_REMOTE_ID = new String[]{
  80. ProviderTableMeta._ID, ProviderTableMeta.FILE_REMOTE_ID
  81. };
  82. private static final String[] PROJECTION_FILE_PATH_AND_OWNER = new String[]{
  83. ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH, ProviderTableMeta.FILE_ACCOUNT_OWNER
  84. };
  85. @Inject protected Clock clock;
  86. @Inject NextcloudDatabase database;
  87. private SupportSQLiteOpenHelper mDbHelper;
  88. private Context mContext;
  89. private UriMatcher mUriMatcher;
  90. @Override
  91. public int delete(@NonNull Uri uri, String where, String[] whereArgs) {
  92. if (isCallerNotAllowed(uri)) {
  93. return -1;
  94. }
  95. int count;
  96. SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
  97. db.beginTransaction();
  98. try {
  99. count = delete(db, uri, where, whereArgs);
  100. db.setTransactionSuccessful();
  101. } finally {
  102. db.endTransaction();
  103. }
  104. mContext.getContentResolver().notifyChange(uri, null);
  105. return count;
  106. }
  107. private int delete(SupportSQLiteDatabase db, Uri uri, String where, String... whereArgs) {
  108. if (isCallerNotAllowed(uri)) {
  109. return -1;
  110. }
  111. // verify where for public paths
  112. switch (mUriMatcher.match(uri)) {
  113. case ROOT_DIRECTORY:
  114. case SINGLE_FILE:
  115. case DIRECTORY:
  116. VerificationUtils.verifyWhere(where);
  117. }
  118. int count;
  119. switch (mUriMatcher.match(uri)) {
  120. case SINGLE_FILE:
  121. count = deleteSingleFile(db, uri, where, whereArgs);
  122. break;
  123. case DIRECTORY:
  124. count = deleteDirectory(db, uri, where, whereArgs);
  125. break;
  126. case ROOT_DIRECTORY:
  127. count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, where, whereArgs);
  128. break;
  129. case SHARES:
  130. count = db.delete(ProviderTableMeta.OCSHARES_TABLE_NAME, where, whereArgs);
  131. break;
  132. case CAPABILITIES:
  133. count = db.delete(ProviderTableMeta.CAPABILITIES_TABLE_NAME, where, whereArgs);
  134. break;
  135. case UPLOADS:
  136. count = db.delete(ProviderTableMeta.UPLOADS_TABLE_NAME, where, whereArgs);
  137. break;
  138. case SYNCED_FOLDERS:
  139. count = db.delete(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, where, whereArgs);
  140. break;
  141. case EXTERNAL_LINKS:
  142. count = db.delete(ProviderTableMeta.EXTERNAL_LINKS_TABLE_NAME, where, whereArgs);
  143. break;
  144. case VIRTUAL:
  145. count = db.delete(ProviderTableMeta.VIRTUAL_TABLE_NAME, where, whereArgs);
  146. break;
  147. case FILESYSTEM:
  148. count = db.delete(ProviderTableMeta.FILESYSTEM_TABLE_NAME, where, whereArgs);
  149. break;
  150. default:
  151. throw new IllegalArgumentException(String.format(Locale.US, "Unknown uri: %s", uri.toString()));
  152. }
  153. return count;
  154. }
  155. private int deleteDirectory(SupportSQLiteDatabase db, Uri uri, String where, String... whereArgs) {
  156. int count = 0;
  157. Cursor children = query(uri, PROJECTION_CONTENT_TYPE, null, null, null);
  158. if (children != null) {
  159. if (children.moveToFirst()) {
  160. long childId;
  161. boolean isDir;
  162. while (!children.isAfterLast()) {
  163. childId = children.getLong(children.getColumnIndexOrThrow(ProviderTableMeta._ID));
  164. isDir = MimeType.DIRECTORY.equals(children.getString(
  165. children.getColumnIndexOrThrow(ProviderTableMeta.FILE_CONTENT_TYPE)
  166. ));
  167. if (isDir) {
  168. count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId),
  169. null, (String[]) null);
  170. } else {
  171. count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId),
  172. null, (String[]) null);
  173. }
  174. children.moveToNext();
  175. }
  176. }
  177. children.close();
  178. }
  179. if (uri.getPathSegments().size() > MINIMUM_PATH_SEGMENTS_SIZE) {
  180. count += deleteWithUri(db, uri, where, whereArgs);
  181. }
  182. return count;
  183. }
  184. private int deleteSingleFile(SupportSQLiteDatabase db, Uri uri, String where, String... whereArgs) {
  185. int count = 0;
  186. try (Cursor c = query(db, uri, PROJECTION_REMOTE_ID, where, whereArgs, null)) {
  187. if (c.moveToFirst()) {
  188. String id = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta._ID));
  189. Log_OC.d(TAG, "Removing FILE " + id);
  190. }
  191. count = deleteWithUri(db, uri, where, whereArgs);
  192. } catch (Exception e) {
  193. Log_OC.d(TAG, "DB-Error removing file!", e);
  194. }
  195. return count;
  196. }
  197. private int deleteWithUri(SupportSQLiteDatabase db, Uri uri, String where, String[] whereArgs) {
  198. final String[] argsWithUri = VerificationUtils.prependUriFirstSegmentToSelectionArgs(whereArgs, uri);
  199. return db.delete(ProviderTableMeta.FILE_TABLE_NAME,
  200. ProviderTableMeta._ID + "=?"
  201. + (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), argsWithUri);
  202. }
  203. @Override
  204. public String getType(@NonNull Uri uri) {
  205. switch (mUriMatcher.match(uri)) {
  206. case ROOT_DIRECTORY:
  207. return ProviderTableMeta.CONTENT_TYPE;
  208. case SINGLE_FILE:
  209. return ProviderTableMeta.CONTENT_TYPE_ITEM;
  210. default:
  211. throw new IllegalArgumentException(String.format(Locale.US, "Unknown Uri id: %s", uri));
  212. }
  213. }
  214. @Override
  215. public Uri insert(@NonNull Uri uri, ContentValues values) {
  216. if (isCallerNotAllowed(uri)) {
  217. return null;
  218. }
  219. Uri newUri;
  220. SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
  221. db.beginTransaction();
  222. try {
  223. newUri = insert(db, uri, values);
  224. db.setTransactionSuccessful();
  225. } finally {
  226. db.endTransaction();
  227. }
  228. mContext.getContentResolver().notifyChange(newUri, null);
  229. return newUri;
  230. }
  231. private Uri insert(SupportSQLiteDatabase db, Uri uri, ContentValues values) {
  232. // verify only for those requests that are not internal (files table)
  233. switch (mUriMatcher.match(uri)) {
  234. case ROOT_DIRECTORY:
  235. case SINGLE_FILE:
  236. case DIRECTORY:
  237. VerificationUtils.verifyColumns(values);
  238. break;
  239. }
  240. switch (mUriMatcher.match(uri)) {
  241. case ROOT_DIRECTORY:
  242. case SINGLE_FILE:
  243. String where = ProviderTableMeta.FILE_PATH + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
  244. String remotePath = values.getAsString(ProviderTableMeta.FILE_PATH);
  245. String accountName = values.getAsString(ProviderTableMeta.FILE_ACCOUNT_OWNER);
  246. String[] whereArgs = {remotePath, accountName};
  247. Cursor doubleCheck = query(db, uri, PROJECTION_FILE_PATH_AND_OWNER, where, whereArgs, null);
  248. // ugly patch; serious refactoring is needed to reduce work in
  249. // FileDataStorageManager and bring it to FileContentProvider
  250. if (!doubleCheck.moveToFirst()) {
  251. doubleCheck.close();
  252. long rowId = db.insert(ProviderTableMeta.FILE_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  253. if (rowId > 0) {
  254. return ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId);
  255. } else {
  256. throw new SQLException(ERROR + uri);
  257. }
  258. } else {
  259. // file is already inserted; race condition, let's avoid a duplicated entry
  260. Uri insertedFileUri = ContentUris.withAppendedId(
  261. ProviderTableMeta.CONTENT_URI_FILE,
  262. doubleCheck.getLong(doubleCheck.getColumnIndexOrThrow(ProviderTableMeta._ID))
  263. );
  264. doubleCheck.close();
  265. return insertedFileUri;
  266. }
  267. case SHARES:
  268. Uri insertedShareUri;
  269. long idShares = db.insert(ProviderTableMeta.OCSHARES_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  270. if (idShares > 0) {
  271. insertedShareUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, idShares);
  272. } else {
  273. throw new SQLException(ERROR + uri);
  274. }
  275. updateFilesTableAccordingToShareInsertion(db, values);
  276. return insertedShareUri;
  277. case CAPABILITIES:
  278. Uri insertedCapUri;
  279. long idCapabilities = db.insert(ProviderTableMeta.CAPABILITIES_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  280. if (idCapabilities > 0) {
  281. insertedCapUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_CAPABILITIES, idCapabilities);
  282. } else {
  283. throw new SQLException(ERROR + uri);
  284. }
  285. return insertedCapUri;
  286. case UPLOADS:
  287. Uri insertedUploadUri;
  288. long uploadId = db.insert(ProviderTableMeta.UPLOADS_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  289. if (uploadId > 0) {
  290. insertedUploadUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_UPLOADS, uploadId);
  291. } else {
  292. throw new SQLException(ERROR + uri);
  293. }
  294. return insertedUploadUri;
  295. case SYNCED_FOLDERS:
  296. Uri insertedSyncedFolderUri;
  297. long syncedFolderId = db.insert(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  298. if (syncedFolderId > 0) {
  299. insertedSyncedFolderUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  300. syncedFolderId);
  301. } else {
  302. throw new SQLException("ERROR " + uri);
  303. }
  304. return insertedSyncedFolderUri;
  305. case EXTERNAL_LINKS:
  306. Uri insertedExternalLinkUri;
  307. long externalLinkId = db.insert(ProviderTableMeta.EXTERNAL_LINKS_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  308. if (externalLinkId > 0) {
  309. insertedExternalLinkUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_EXTERNAL_LINKS,
  310. externalLinkId);
  311. } else {
  312. throw new SQLException("ERROR " + uri);
  313. }
  314. return insertedExternalLinkUri;
  315. case VIRTUAL:
  316. Uri insertedVirtualUri;
  317. long virtualId = db.insert(ProviderTableMeta.VIRTUAL_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  318. if (virtualId > 0) {
  319. insertedVirtualUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_VIRTUAL, virtualId);
  320. } else {
  321. throw new SQLException("ERROR " + uri);
  322. }
  323. return insertedVirtualUri;
  324. case FILESYSTEM:
  325. Uri insertedFilesystemUri;
  326. long filesystemId = db.insert(ProviderTableMeta.FILESYSTEM_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values);
  327. if (filesystemId > 0) {
  328. insertedFilesystemUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILESYSTEM,
  329. filesystemId);
  330. } else {
  331. throw new SQLException("ERROR " + uri);
  332. }
  333. return insertedFilesystemUri;
  334. default:
  335. throw new IllegalArgumentException("Unknown uri id: " + uri);
  336. }
  337. }
  338. private void updateFilesTableAccordingToShareInsertion(SupportSQLiteDatabase db, ContentValues newShare) {
  339. ContentValues fileValues = new ContentValues();
  340. ShareType newShareType = ShareType.fromValue(newShare.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE));
  341. switch (newShareType) {
  342. case PUBLIC_LINK:
  343. fileValues.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, 1);
  344. break;
  345. case USER:
  346. case GROUP:
  347. case EMAIL:
  348. case FEDERATED:
  349. case FEDERATED_GROUP:
  350. case ROOM:
  351. case CIRCLE:
  352. case DECK:
  353. case GUEST:
  354. fileValues.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, 1);
  355. break;
  356. default:
  357. // everything should be handled
  358. }
  359. String where = ProviderTableMeta.FILE_PATH + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
  360. String[] whereArgs = new String[]{
  361. newShare.getAsString(ProviderTableMeta.OCSHARES_PATH),
  362. newShare.getAsString(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER)
  363. };
  364. db.update(ProviderTableMeta.FILE_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, fileValues, where, whereArgs);
  365. }
  366. @Override
  367. public boolean onCreate() {
  368. AndroidInjection.inject(this);
  369. mDbHelper = database.getOpenHelper();
  370. mContext = getContext();
  371. if (mContext == null) {
  372. return false;
  373. }
  374. String authority = mContext.getResources().getString(R.string.authority);
  375. mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  376. mUriMatcher.addURI(authority, null, ROOT_DIRECTORY);
  377. mUriMatcher.addURI(authority, "file/", SINGLE_FILE);
  378. mUriMatcher.addURI(authority, "file/#", SINGLE_FILE);
  379. mUriMatcher.addURI(authority, "dir/", DIRECTORY);
  380. mUriMatcher.addURI(authority, "dir/#", DIRECTORY);
  381. mUriMatcher.addURI(authority, "shares/", SHARES);
  382. mUriMatcher.addURI(authority, "shares/#", SHARES);
  383. mUriMatcher.addURI(authority, "capabilities/", CAPABILITIES);
  384. mUriMatcher.addURI(authority, "capabilities/#", CAPABILITIES);
  385. mUriMatcher.addURI(authority, "uploads/", UPLOADS);
  386. mUriMatcher.addURI(authority, "uploads/#", UPLOADS);
  387. mUriMatcher.addURI(authority, "synced_folders", SYNCED_FOLDERS);
  388. mUriMatcher.addURI(authority, "external_links", EXTERNAL_LINKS);
  389. mUriMatcher.addURI(authority, "virtual", VIRTUAL);
  390. mUriMatcher.addURI(authority, "filesystem", FILESYSTEM);
  391. return true;
  392. }
  393. @Override
  394. public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
  395. String sortOrder) {
  396. // skip check for files as they need to be queried to get access via document provider
  397. switch (mUriMatcher.match(uri)) {
  398. case ROOT_DIRECTORY:
  399. case SINGLE_FILE:
  400. case DIRECTORY:
  401. break;
  402. default:
  403. if (isCallerNotAllowed(uri)) {
  404. return null;
  405. }
  406. }
  407. Cursor result;
  408. SupportSQLiteDatabase db = mDbHelper.getReadableDatabase();
  409. db.beginTransaction();
  410. try {
  411. result = query(db, uri, projection, selection, selectionArgs, sortOrder);
  412. db.setTransactionSuccessful();
  413. } finally {
  414. db.endTransaction();
  415. }
  416. return result;
  417. }
  418. private Cursor query(SupportSQLiteDatabase db,
  419. Uri uri,
  420. String[] projectionArray,
  421. String selection,
  422. String[] selectionArgs,
  423. String sortOrder) {
  424. // verify only for those requests that are not internal
  425. final int uriMatch = mUriMatcher.match(uri);
  426. String tableName;
  427. switch (uriMatch) {
  428. case ROOT_DIRECTORY:
  429. case DIRECTORY:
  430. case SINGLE_FILE:
  431. VerificationUtils.verifyWhere(selection); // prevent injection in public paths
  432. tableName = ProviderTableMeta.FILE_TABLE_NAME;
  433. break;
  434. case SHARES:
  435. tableName = ProviderTableMeta.OCSHARES_TABLE_NAME;
  436. break;
  437. case CAPABILITIES:
  438. tableName = ProviderTableMeta.CAPABILITIES_TABLE_NAME;
  439. break;
  440. case UPLOADS:
  441. tableName = ProviderTableMeta.UPLOADS_TABLE_NAME;
  442. break;
  443. case SYNCED_FOLDERS:
  444. tableName = ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME;
  445. break;
  446. case EXTERNAL_LINKS:
  447. tableName = ProviderTableMeta.EXTERNAL_LINKS_TABLE_NAME;
  448. break;
  449. case VIRTUAL:
  450. tableName = ProviderTableMeta.VIRTUAL_TABLE_NAME;
  451. break;
  452. case FILESYSTEM:
  453. tableName = ProviderTableMeta.FILESYSTEM_TABLE_NAME;
  454. break;
  455. default:
  456. throw new IllegalArgumentException("Unknown uri id: " + uri);
  457. }
  458. SupportSQLiteQueryBuilder queryBuilder = SupportSQLiteQueryBuilder.builder(tableName);
  459. // add ID to arguments if Uri has more than one segment
  460. if (uriMatch != ROOT_DIRECTORY && uri.getPathSegments().size() > SINGLE_PATH_SEGMENT) {
  461. String idColumn = uriMatch == DIRECTORY ? ProviderTableMeta.FILE_PARENT : ProviderTableMeta._ID;
  462. selection = idColumn + "=? AND " + selection;
  463. selectionArgs = VerificationUtils.prependUriFirstSegmentToSelectionArgs(selectionArgs, uri);
  464. }
  465. String order;
  466. if (TextUtils.isEmpty(sortOrder)) {
  467. switch (uriMatch) {
  468. case SHARES:
  469. order = ProviderTableMeta.OCSHARES_DEFAULT_SORT_ORDER;
  470. break;
  471. case CAPABILITIES:
  472. order = ProviderTableMeta.CAPABILITIES_DEFAULT_SORT_ORDER;
  473. break;
  474. case UPLOADS:
  475. order = ProviderTableMeta.UPLOADS_DEFAULT_SORT_ORDER;
  476. break;
  477. case SYNCED_FOLDERS:
  478. order = ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH;
  479. break;
  480. case EXTERNAL_LINKS:
  481. order = ProviderTableMeta.EXTERNAL_LINKS_NAME;
  482. break;
  483. case VIRTUAL:
  484. order = ProviderTableMeta.VIRTUAL_TYPE;
  485. break;
  486. default: // Files
  487. order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
  488. break;
  489. case FILESYSTEM:
  490. order = ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH;
  491. break;
  492. }
  493. } else {
  494. if (uriMatch == ROOT_DIRECTORY || uriMatch == SINGLE_FILE || uriMatch == DIRECTORY) {
  495. VerificationUtils.verifySortOrder(sortOrder);
  496. }
  497. order = sortOrder;
  498. }
  499. // DB case_sensitive
  500. db.execSQL("PRAGMA case_sensitive_like = true");
  501. // only file list is publicly accessible via content provider, so only this has to be protected
  502. if ((uriMatch == ROOT_DIRECTORY || uriMatch == SINGLE_FILE ||
  503. uriMatch == DIRECTORY) && projectionArray != null && projectionArray.length > 0) {
  504. for (String column : projectionArray) {
  505. VerificationUtils.verifyColumnName(column);
  506. }
  507. }
  508. // if both are null, let them pass to query
  509. if (selectionArgs == null && selection != null) {
  510. selectionArgs = new String[]{selection};
  511. selection = "(?)";
  512. }
  513. if (!TextUtils.isEmpty(selection)) {
  514. queryBuilder.selection(selection, selectionArgs);
  515. }
  516. if (!TextUtils.isEmpty(order)) {
  517. queryBuilder.orderBy(order);
  518. }
  519. if (projectionArray != null && projectionArray.length > 0) {
  520. queryBuilder.columns(projectionArray);
  521. }
  522. final SupportSQLiteQuery supportSQLiteQuery = queryBuilder.create();
  523. final Cursor c = db.query(supportSQLiteQuery);
  524. c.setNotificationUri(mContext.getContentResolver(), uri);
  525. return c;
  526. }
  527. @Override
  528. public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
  529. if (isCallerNotAllowed(uri)) {
  530. return -1;
  531. }
  532. int count;
  533. SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
  534. db.beginTransaction();
  535. try {
  536. count = update(db, uri, values, selection, selectionArgs);
  537. db.setTransactionSuccessful();
  538. } finally {
  539. db.endTransaction();
  540. }
  541. mContext.getContentResolver().notifyChange(uri, null);
  542. return count;
  543. }
  544. private int update(SupportSQLiteDatabase db, Uri uri, ContentValues values, String selection, String... selectionArgs) {
  545. // verify contentValues and selection for public paths to prevent injection
  546. switch (mUriMatcher.match(uri)) {
  547. case ROOT_DIRECTORY:
  548. case SINGLE_FILE:
  549. case DIRECTORY:
  550. VerificationUtils.verifyColumns(values);
  551. VerificationUtils.verifyWhere(selection);
  552. }
  553. switch (mUriMatcher.match(uri)) {
  554. case DIRECTORY:
  555. return 0;
  556. case SHARES:
  557. return db.update(ProviderTableMeta.OCSHARES_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values, selection, selectionArgs);
  558. case CAPABILITIES:
  559. return db.update(ProviderTableMeta.CAPABILITIES_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values, selection, selectionArgs);
  560. case UPLOADS:
  561. return db.update(ProviderTableMeta.UPLOADS_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values, selection, selectionArgs);
  562. case SYNCED_FOLDERS:
  563. return db.update(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values, selection, selectionArgs);
  564. case FILESYSTEM:
  565. return db.update(ProviderTableMeta.FILESYSTEM_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values, selection, selectionArgs);
  566. default:
  567. return db.update(ProviderTableMeta.FILE_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, values, selection, selectionArgs);
  568. }
  569. }
  570. @NonNull
  571. @Override
  572. public ContentProviderResult[] applyBatch(@NonNull ArrayList<ContentProviderOperation> operations)
  573. throws OperationApplicationException {
  574. Log_OC.d("FileContentProvider", "applying batch in provider " + this +
  575. " (temporary: " + isTemporary() + ")");
  576. ContentProviderResult[] results = new ContentProviderResult[operations.size()];
  577. int i = 0;
  578. SupportSQLiteDatabase database = mDbHelper.getWritableDatabase();
  579. database.beginTransaction(); // it's supposed that transactions can be nested
  580. try {
  581. for (ContentProviderOperation operation : operations) {
  582. results[i] = operation.apply(this, results, i);
  583. i++;
  584. }
  585. database.setTransactionSuccessful();
  586. } finally {
  587. database.endTransaction();
  588. }
  589. Log_OC.d("FileContentProvider", "applied batch in provider " + this);
  590. return results;
  591. }
  592. private boolean isCallerNotAllowed(Uri uri) {
  593. switch (mUriMatcher.match(uri)) {
  594. case SHARES:
  595. case CAPABILITIES:
  596. case UPLOADS:
  597. case SYNCED_FOLDERS:
  598. case EXTERNAL_LINKS:
  599. case VIRTUAL:
  600. case FILESYSTEM:
  601. String callingPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
  602. return callingPackage == null || !callingPackage.equals(mContext.getPackageName());
  603. case ROOT_DIRECTORY:
  604. case SINGLE_FILE:
  605. case DIRECTORY:
  606. default:
  607. return false;
  608. }
  609. }
  610. static class VerificationUtils {
  611. private static boolean isValidColumnName(@NonNull String columnName) {
  612. return ProviderTableMeta.FILE_ALL_COLUMNS.contains(columnName);
  613. }
  614. @VisibleForTesting
  615. public static void verifyColumns(@Nullable ContentValues contentValues) {
  616. if (contentValues == null || contentValues.keySet().isEmpty()) {
  617. return;
  618. }
  619. for (String name : contentValues.keySet()) {
  620. verifyColumnName(name);
  621. }
  622. }
  623. public static void verifyColumnName(@NonNull String columnName) {
  624. if (!isValidColumnName(columnName)) {
  625. throw new IllegalArgumentException(String.format("Column name \"%s\" is not allowed", columnName));
  626. }
  627. }
  628. public static String[] prependUriFirstSegmentToSelectionArgs(@Nullable final String[] originalArgs, final Uri uri) {
  629. String[] args;
  630. if (originalArgs == null) {
  631. args = new String[1];
  632. } else {
  633. args = new String[originalArgs.length + 1];
  634. System.arraycopy(originalArgs, 0, args, 1, originalArgs.length);
  635. }
  636. args[0] = uri.getPathSegments().get(1);
  637. return args;
  638. }
  639. public static void verifySortOrder(@Nullable String sortOrder) {
  640. if (sortOrder == null) {
  641. return;
  642. }
  643. SQLiteTokenizer.tokenize(sortOrder, SQLiteTokenizer.OPTION_NONE, VerificationUtils::verifySortToken);
  644. }
  645. private static void verifySortToken(String token){
  646. // accept empty tokens and valid column names
  647. if (TextUtils.isEmpty(token) || isValidColumnName(token)) {
  648. return;
  649. }
  650. // accept only a small subset of keywords
  651. if(SQLiteTokenizer.isKeyword(token)){
  652. switch (token.toUpperCase(Locale.ROOT)) {
  653. case "ASC":
  654. case "DESC":
  655. case "COLLATE":
  656. case "NOCASE":
  657. return;
  658. }
  659. }
  660. // if none of the above, invalid token
  661. throw new IllegalArgumentException("Invalid token " + token);
  662. }
  663. public static void verifyWhere(@Nullable String where) {
  664. if (where == null) {
  665. return;
  666. }
  667. SQLiteTokenizer.tokenize(where, SQLiteTokenizer.OPTION_NONE, VerificationUtils::verifyWhereToken);
  668. }
  669. private static void verifyWhereToken(String token) {
  670. // allow empty, valid column names, functions (min,max,count) and types
  671. if (TextUtils.isEmpty(token) || isValidColumnName(token)
  672. || SQLiteTokenizer.isFunction(token) || SQLiteTokenizer.isType(token)) {
  673. return;
  674. }
  675. // Disallow dangerous keywords, allow others
  676. if (SQLiteTokenizer.isKeyword(token)) {
  677. switch (token.toUpperCase(Locale.ROOT)) {
  678. case "SELECT":
  679. case "FROM":
  680. case "WHERE":
  681. case "GROUP":
  682. case "HAVING":
  683. case "WINDOW":
  684. case "VALUES":
  685. case "ORDER":
  686. case "LIMIT":
  687. throw new IllegalArgumentException("Invalid token " + token);
  688. default:
  689. return;
  690. }
  691. }
  692. // if none of the above: invalid token
  693. throw new IllegalArgumentException("Invalid token " + token);
  694. }
  695. }
  696. }