upload method
Uploads content to url using HTTP PUT with DPoP authentication.
Uses content.contentType as the Content-Type header, fixing the
previous hardcoded text/turtle limitation.
Implementation
Future<RemoteUploadResult> upload(
String url,
RawContent content, {
bool requiresAuth = true,
String? ifMatch,
required IriTerm documentIri,
}) async {
final dpop = requiresAuth
? await _authProvider.getDpopToken(_prepareUrlForDpopToken(url), 'PUT')
: null;
_log.fine(
'PUT $url auth=$requiresAuth ifMatch=$ifMatch contentType=${content.contentType}');
final response = await _client.put(
Uri.parse(url),
body: switch (content) {
TextContent(:final text) => text,
BinaryContent(:final bytes) => bytes,
},
headers: {
'Content-Type': content.contentType,
if (dpop != null) 'Authorization': 'DPoP ${dpop.accessToken}',
if (dpop != null) 'DPoP': dpop.dPoP,
if (ifMatch != null) 'If-Match': ifMatch,
if (ifMatch == null) 'If-None-Match': '*',
},
);
_log.fine('Response status: ${response.statusCode}');
if (response.statusCode == 401) {
_log.warning('401 Unauthorized for $url');
throw AuthException(
'Solid Pod authentication failed for $url',
cause: 'HTTP 401 Unauthorized',
);
}
if (response.statusCode == 404) {
throw NotFoundException('Resource not found at $url');
}
if (response.statusCode == 409) {
// 409 Conflict is not an ETag mismatch — it signals a persistent
// server-side conflict (e.g. WebDAV lock, invalid resource state).
// Retrying will not help, so treat as an error.
throw SolidClientException('Failed to upload to $url: 409 Conflict');
}
if (response.statusCode == 412) {
// 412 Precondition Failed: If-Match ETag mismatch — optimistic locking
// conflict. The resource was modified concurrently; caller should
// re-read, re-merge and retry.
return RemoteUploadResult.conflict(
documentIri: documentIri,
requestETag: ifMatch,
);
}
if (response.statusCode >= 200 && response.statusCode < 300) {
var etag = response.headers['etag'];
if (etag == null) {
_log.fine('No ETag in PUT response from $url, fetching via HEAD');
etag = await _fetchETag(url, requiresAuth: requiresAuth);
if (etag == null) {
_log.warning('Could not fetch ETag via HEAD for $url');
}
}
return RemoteUploadResult.success(
etag ?? '',
documentIri: documentIri,
requestETag: ifMatch,
);
}
_log.warning('Failed to upload to $url: ${response.statusCode}');
_log.warning('Response body: ${response.body}');
throw SolidClientException(
'Failed to upload to $url: ${response.statusCode}');
}