feature (public link retrievel)

This commit is contained in:
nvrl committed 2026-07-15 20:37:00 +02:00
1 parent 64f977edb3
commit 2fa9b90265
2 files changed
+41 -12

No files matched your search

+36 -12
View File
@@ -54,10 +54,10 @@ use crate::dtos::{
NodeNameAvailabilityRequest, NodeNameAvailabilityResponse, PhotosAttributesDto,
RenameLinkRequest, RevisionCreationRequest, RevisionCreationResponse, RevisionDto,
RevisionResponse, RevisionUpdateRequest, ShareInvitationDto, ShareInvitationsResponse,
ShareMembersResponse, ShareResponse, ShareTargetType, ShareUrlsResponse, SharedWithMeResponse,
ThumbnailBlockListRequest, ThumbnailBlockListResponse, ThumbnailCreationRequest, ThumbnailDto,
TimelinePhotoListResponse, UpdatePermissionsRequest, VolumeCreationRequest, VolumeEventDto,
VolumeEventListResponse, VolumeTrashResponse,
ShareMembersResponse, ShareResponse, ShareTargetType, ShareUrlDto, ShareUrlsResponse,
SharedWithMeResponse, ThumbnailBlockListRequest, ThumbnailBlockListResponse,
ThumbnailCreationRequest, ThumbnailDto, TimelinePhotoListResponse, UpdatePermissionsRequest,
VolumeCreationRequest, VolumeEventDto, VolumeEventListResponse, VolumeTrashResponse,
};
use crate::events::{DriveEvent, DriveEventScopeId};
use crate::node::{FileThumbnail, Node, NodeKind, RevisionState, Thumbnail, ThumbnailType};
@@ -1889,22 +1889,23 @@ impl ProtonDriveClient {
})
}
/// The public link on a node, if one exists (metadata only — the secret URL
/// fragment is not reconstructed).
/// The public link on a node, if one exists. The secret URL fragment is
/// recovered by decrypting the stored link password with the owner's address
/// key and taking the generated (non-custom) portion — the same recovery the
/// web client does when re-displaying an existing link.
pub async fn get_public_link(&self, uid: &NodeUid) -> Result<Option<PublicLink>> {
let mut timer = self.telemetry.start("get_public_link");
let link = match self.node_share_id(uid).await? {
Some(share_id) => {
let path = format!("shares/{share_id}/urls");
let response: ShareUrlsResponse = self.http.get(&path).await?;
response
.share_urls
.into_iter()
.next()
.map(|dto| PublicLink {
match response.share_urls.into_iter().next() {
Some(dto) => {
let url = self.recover_public_link_url(&dto).await;
Some(PublicLink {
share_id,
public_link_id: dto.share_url_id,
url: None,
url,
role: MemberRole::from_permissions(dto.permissions),
creation_time: dto.create_time,
expiration_time: dto.expiration_time,
@@ -1912,11 +1913,34 @@ impl ProtonDriveClient {
})
}
None => None,
}
}
None => None,
};
timer.success();
Ok(link)
}
/// Rebuild the full public URL (`{public_url}#{generated}`) for an existing
/// link by decrypting its stored `Password` with the owner's address key. The
/// stored secret is the generated password with any custom password appended;
/// only the leading generated portion belongs in the URL fragment (the
/// recipient supplies the custom password separately). Returns `None` if the
/// link carries no password or it can't be decrypted, so the caller still gets
/// the link's metadata.
async fn recover_public_link_url(&self, dto: &ShareUrlDto) -> Option<String> {
if dto.public_url.is_empty() {
return None;
}
let enc = dto.password.as_deref()?;
let (_, email, _) = self.membership_address().await.ok()?;
let (address_keys, _) = self.own_address_keys(&email).await.ok()?;
let bytes = decrypt_armored_with_keys(enc, &address_keys).ok()?;
let full = String::from_utf8_lossy(&bytes);
let generated: String = full.chars().take(PUBLIC_LINK_PASSWORD_LEN).collect();
Some(format!("{}#{generated}", dto.public_url))
}
/// Remove a public link, revoking access via the URL
/// (`DELETE shares/{sid}/urls/{urlID}`).
pub async fn remove_public_link(&self, link: &PublicLink) -> Result<()> {
+5
View File
@@ -1522,6 +1522,11 @@ pub struct ShareUrlDto {
pub share_id: Option<ShareId>,
#[serde(rename = "PublicUrl", default)]
pub public_url: String,
/// The link password (generated portion + any custom password), PGP-encrypted
/// to the share creator's address key. Decrypting it recovers the secret URL
/// fragment. Absent on the create response, present when listing.
#[serde(rename = "Password", default)]
pub password: Option<String>,
#[serde(rename = "CreateTime", default)]
pub create_time: i64,
#[serde(rename = "ExpirationTime", default)]