Skip to content
Open
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
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ Visit the package at [pub.dev](https://pub.dev/packages/solidpod).

## 1.0

+ Preserve encryption state on overwrite in writePod/writeExternalPod [1.0.14 20260730 jesscmoore]
+ Add load test to the example app [1.0.13 20260702 tonypioneer]
+ Migrate TEMPALTE to solidui [1.0.12 20260629 tonypioneer]
+ Migrate TEMPLATE to solidui [1.0.12 20260629 tonypioneer]
+ Support profile editing [1.0.11 20260626 tonypioneer]
+ Bug fix template for dart run [1.0.10 20260622 tonypioneer]
+ Add app template for a 'create' experience [1.0.9 20260619 tonypioneer]
Expand All @@ -32,8 +33,8 @@ Visit the package at [pub.dev](https://pub.dev/packages/solidpod).
+ Check missing resources [0.12.9 20260520 tonypioneer]
+ Support checking webID [0.12.8 20260520 tonypioneer]
+ Update Try Another WebID workflow [0.12.7 20260520 tonypioneer]
+ Bug fix to ttl rdf for special chars #628 [0.12.6 20260518 tonypioneer]
+ Upgrade solidauth and fix key file saving edge cases [0.12.5 20260427 jesscmoore]
+ Bug fix to ttl RDF for special chars #628 [0.12.6 20260518 tonypioneer]
+ Upgrade solid_auth and fix key file saving edge cases [0.12.5 20260427 jesscmoore]
+ Support user profile. [0.12.4 20260421 tonypioneer]
+ Key map + paths updates. Update file_picker. [0.12.3 20260420 jesscmoore]
+ Add silentLogout() [0.12.2 20260325 tonypioneer]
Expand Down
26 changes: 22 additions & 4 deletions lib/src/solid/write_external_pod.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import 'dart:convert';
import 'package:flutter/material.dart' hide Key;

import 'package:solidpod/src/solid/api/rest_api.dart';
import 'package:solidpod/src/solid/check_encryption.dart' show isContentEncrypted;
import 'package:solidpod/src/solid/common_func.dart';
import 'package:solidpod/src/solid/constants/common.dart';
import 'package:solidpod/src/solid/utils/exceptions.dart';
Expand All @@ -44,7 +45,17 @@ import 'package:solidpod/src/solid/utils/misc.dart';

/// Write file [fileUrl] with content [fileContent] to an external PODs in the
/// data directory (within potential subdirectories encoded in [fileUrl]).
/// The content will be encrypted if the original content is true.
///
/// [encrypted] defaults to `null`, meaning "not specified by the caller": when
/// overwriting an existing file, the file's *current* at-rest state on the
/// server is mirrored (plaintext stays plaintext, ciphertext stays
/// ciphertext) rather than always re-encrypting just because a shared
/// individual key happens to be on record. This matters for a resource the
/// owner decrypted in place for Public/Authenticated User sharing (see
/// `decryptFileInPlace` in solidpod) — without this, a recipient with write
/// access editing the file would silently re-encrypt it and break that
/// sharing grant. Pass `true`/`false` explicitly to force a specific
/// encryption state regardless of what's currently on the server.
///
/// The encryption boilerplate shared with [writePod] is factored out into
/// [getEncTTLStrWithRandomIV], and the "own POD vs external POD" routing is
Expand All @@ -57,7 +68,7 @@ Future<void> writeExternalPod(
String fileUrl,
String fileContent,
String fileOwnerWebId, {
bool encrypted = true,
bool? encrypted,
bool overwrite = true,
String? inheritKeyFrom,
}) async {
Expand Down Expand Up @@ -89,9 +100,16 @@ Future<void> writeExternalPod(
case ResourceStatus.exist:
final remoteFileContent = utf8.decode(await getResource(fileUrl));

// When the caller didn't specify [encrypted], mirror whatever is
// actually on the server right now instead of assuming a shared key
// on record means the file should be (re-)encrypted — the owner may
// have decrypted it in place for Public/Authenticated User sharing.
final wantEncrypted = encrypted ??
isContentEncrypted(fileUrl: fileUrl, content: remoteFileContent);

final key = await KeyManager.getSharedIndividualKey(fileUrl);

if (key != null) {
if (wantEncrypted && key != null) {
// Get file path
// final filePath =
// fileUrl.replaceAll(fileOwnerWebId.replaceAll(profCard, ''), '');
Expand All @@ -108,7 +126,7 @@ Future<void> writeExternalPod(
'but the extension of provided filename "$fileUrl" is not ".ttl"',
);
}
} else if (hasInheritedKey(remoteFileContent, fileUrl)) {
} else if (wantEncrypted && hasInheritedKey(remoteFileContent, fileUrl)) {
// Get file path
// final filePath =
// fileUrl.replaceAll(fileOwnerWebId.replaceAll(profCard, ''), '');
Expand Down
44 changes: 40 additions & 4 deletions lib/src/solid/write_pod.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@

library;

import 'dart:convert' show utf8;

import 'package:flutter/foundation.dart' show debugPrint;

import 'package:encrypter_plus/encrypter_plus.dart' show Key;
import 'package:mime/mime.dart' as mime;

import 'package:solidpod/src/solid/api/rest_api.dart';
import 'package:solidpod/src/solid/check_encryption.dart'
show isContentEncrypted;
import 'package:solidpod/src/solid/constants/common.dart';
import 'package:solidpod/src/solid/constants/path_type.dart';
import 'package:solidpod/src/solid/utils/exceptions.dart';
Expand All @@ -58,7 +62,18 @@ import 'package:solidpod/src/solid/write_external_pod.dart'
/// Arguments:
/// - [filePath]: The path (relative to appname/data/) of the file to write
/// - [fileContent]: The content to write to the file
/// - [encrypted]: Whether to encrypt the file content (default: true)
/// - [encrypted]: Whether to encrypt the file content. Defaults to `null`,
/// meaning "not specified by the caller": for a new file (or when
/// [overwrite] is false) this behaves as `true`; when [overwrite] is true
/// and the file already exists, the file's *current* at-rest state on the
/// server is mirrored instead (plaintext stays plaintext, ciphertext stays
/// ciphertext). This matters for a resource that was decrypted in place
/// for Public/Authenticated User sharing (see `decryptFileInPlace`) —
/// without this, an unrelated edit would silently re-encrypt it and break
/// that sharing grant, since a class-based ACL grant has no key to
/// decrypt with. Pass `true`/`false` explicitly to override this and
/// force a specific encryption state regardless of what's currently on
/// the server.
/// - [createAcl]: Whether to create a separate acl for the resource (default: true)
/// - [overwrite]: Whether to overwrite the content of an existing file (default: false)
/// - [pathType]: Optional type of relative path (for both [filePath] and [inheritKeyFrom])
Expand All @@ -80,7 +95,7 @@ import 'package:solidpod/src/solid/write_external_pod.dart'
Future<void> writePod(
String filePath,
String fileContent, {
bool encrypted = true,
bool? encrypted,
bool createAcl = true,
bool overwrite = false,
PathType pathType = PathType.relativeToData,
Expand Down Expand Up @@ -133,6 +148,27 @@ Future<void> writePod(
);
}

final status = await checkResourceStatus(fileUrl);

// Resolve the effective encryption flag. When the caller didn't specify
// [encrypted] and this is an overwrite of an existing file, mirror the
// file's current at-rest state instead of assuming `true` — otherwise an
// unrelated edit would silently re-encrypt a file that was deliberately
// decrypted in place for Public/Authenticated User sharing (see
// `decryptFileInPlace`), stranding a resource whose ACL still promises
// open access but whose bytes no longer are.

var resolvedEncrypted = encrypted ?? true;
if (encrypted == null &&
inheritKeyFrom == null &&
overwrite &&
status == ResourceStatus.exist) {
final currentContent = utf8.decode(await getResource(fileUrl));
// Determine current encryption state
resolvedEncrypted =
isContentEncrypted(fileUrl: fileUrl, content: currentContent);
}

Key? encKey;
String? inheritKeyUrl;
if (inheritKeyFrom != null) {
Expand All @@ -143,7 +179,7 @@ Future<void> writePod(
);
}

if (encrypted || inheritKeyFrom != null) {
if (resolvedEncrypted || inheritKeyFrom != null) {
if (!fileUrl.endsWith('.ttl')) {
throw Exception(
'Encrypted text file should be in turtle format, '
Expand All @@ -154,7 +190,7 @@ Future<void> writePod(
encKey = await configureEncKey(fileUrl, inheritKeyUrl: inheritKeyUrl);
}

switch (await checkResourceStatus(fileUrl)) {
switch (status) {
case ResourceStatus.exist:
if (overwrite) {
debugPrint('NOTE: Overwriting existing file "$filePath"');
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: solidpod
description: Support access to private data from PODs on Solid servers.
version: 1.0.13
version: 1.0.14
homepage: https://github.com/anusii/solidpod

environment:
Expand Down
Loading