Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pir/pir-internal/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ dependencies {
implementation AndroidX.webkit
implementation Google.android.material
implementation Google.dagger
implementation AndroidX.room.runtime
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import com.duckduckgo.pir.impl.store.PirRepository
import com.duckduckgo.pir.impl.store.PirSchedulingRepository
import com.duckduckgo.pir.internal.R
import com.duckduckgo.pir.internal.databinding.ActivityPirInternalScanBinding
import com.duckduckgo.pir.internal.settings.store.secure.PirDatabaseExporter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -77,6 +78,9 @@ class PirDevScanActivity : DuckDuckGoActivity() {
@Inject
lateinit var currentTimeProvider: CurrentTimeProvider

@Inject
lateinit var pirDatabaseExporter: PirDatabaseExporter

private val binding: ActivityPirInternalScanBinding by viewBinding()
private val recordStringBuilder = StringBuilder()

Expand Down Expand Up @@ -197,6 +201,12 @@ class PirDevScanActivity : DuckDuckGoActivity() {
pirScanScheduler.scheduleScans()
Toast.makeText(this, getString(R.string.pirMessageSchedule), Toast.LENGTH_SHORT).show()
}

binding.debugExportDb.setOnClickListener {
lifecycleScope.launch(dispatcherProvider.io()) {
pirDatabaseExporter.exportToPlaintext()
}
}
}

private fun killRunningWork() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.pir.internal.settings.store.secure

import android.content.Context
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.pir.impl.store.PirDatabase
import com.squareup.anvil.annotations.ContributesBinding
import dagger.SingleInstanceIn
import kotlinx.coroutines.withContext
import logcat.LogPriority.ERROR
import logcat.asLog
import logcat.logcat
import java.io.File
import javax.inject.Inject

/**
* Utility interface for exporting the encrypted PIR database to a plaintext format.
* Useful for debugging and data inspection.
*/
interface PirDatabaseExporter {
/**
* Exports the encrypted database to a plaintext (unencrypted) SQLite database
* on the device's external storage.
*
* You can pull it using "adb pull /storage/emulated/0/Android/data/com.duckduckgo.mobile.android.debug/files/pir_decrypted.db ~/Desktop/"
* and open it in a SQLite viewer for inspection.
*/
suspend fun exportToPlaintext()
}

@SingleInstanceIn(AppScope::class)
@ContributesBinding(
scope = AppScope::class,
boundType = PirDatabaseExporter::class,
)
class PirRealDatabaseExporter @Inject constructor(
private val pirDatabase: PirDatabase,
private val dispatcherProvider: DispatcherProvider,
private val context: Context,
) : PirDatabaseExporter {

override suspend fun exportToPlaintext() = withContext(dispatcherProvider.io()) {
exportDecryptedDatabase()
}

private fun exportDecryptedDatabase() {
try {
// Get the writable database instance
val db = pirDatabase.openHelper.writableDatabase

// Define output path - using external files directory for accessibility
val outputFile = File(context.getExternalFilesDir(null), "pir_decrypted.db")

// Delete existing file if present
if (outputFile.exists()) {
outputFile.delete()
logcat { "PIR-DB: Deleted existing decrypted database file" }
}

val outputPath = outputFile.absolutePath
logcat { "PIR-DB: Exporting decrypted database to: $outputPath" }

// Attach a plaintext database (no encryption key)
db.query("ATTACH DATABASE ? AS plaintext KEY ''", arrayOf(outputPath)).use { cursor ->
cursor.moveToFirst()
}

logcat { "PIR-DB: Attached plaintext database" }

// Export the encrypted database to the plaintext one
db.query("SELECT sqlcipher_export('plaintext')").use { cursor ->
if (cursor.moveToFirst()) {
val result = cursor.getString(0)
logcat { "PIR-DB: Export result: $result" }
}
}

logcat { "PIR-DB: Database exported successfully" }

// Detach the plaintext database
db.query("DETACH DATABASE plaintext").use { cursor ->
cursor.moveToFirst()
}

logcat { "PIR-DB: Detached plaintext database" }

// Verify the file was created and has content
if (outputFile.exists() && outputFile.length() > 0) {
logcat { "PIR-DB: Successfully exported decrypted database (${outputFile.length()} bytes)" }
logcat { "PIR-DB: File location: ${outputFile.absolutePath}" }
} else {
logcat(ERROR) { "PIR-DB: Export file was not created or is empty" }
}
} catch (e: Exception) {
logcat(ERROR) { "PIR-DB: Error exporting database: ${e.asLog()}" }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@
app:editable="true"
app:type="single_line" />

<com.duckduckgo.common.ui.view.button.DaxButtonPrimary
android:id="@+id/debugExportDb"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/pirDevExportDb"
app:editable="true"
app:type="single_line" />

<com.duckduckgo.common.ui.view.listitem.SectionHeaderListItem
android:layout_width="wrap_content"
Expand Down Expand Up @@ -204,4 +212,4 @@
android:text="@string/pirDevViewScanResults" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</LinearLayout>
1 change: 1 addition & 0 deletions pir/pir-internal/src/main/res/values/donottranslate.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<string name="pirDevViewOptOutResults">View All OptOut Results</string>
<string name="pirDevViewEmailResults">View Email Confirmation Results</string>
<string name="pirDevViewRunEvents">View PIR Run events</string>
<string name="pirDevExportDb">Export database</string>
<string name="pirDevViewEmailEvents">PIR Email</string>
<string name="pirDevSchedule">Schedule Scan</string>
<string name="pirDevSimpleScanHeader">Simple Scan Results</string>
Expand Down
Loading