feat(android): an entry can get its icon, through the same relay as the desktop
Fetching https://<site>/favicon.ico directly would resolve a DNS name - and open a connection - to EVERY domain kept in the vault. The list of sites where someone holds an account is precisely what this vault protects; handing it to the network for a sixteen-pixel drawing would be a poor trade. The relay sees only that someone is interested in one domain, which is why the call is asked for entry by entry and never happens on its own. Two tries: the full host, then the second-level domain - chat.qwen.ai has no icon at the relay, qwen.ai does. The type comes from the first bytes, never from the extension: the relay answers PNG under a .ico address. A dedicated patch rather than PatchEntryField, because icon_custom is a NUMBER in the snapshot and writing it as a string would give "0" - which the desktop reads with GetValue<Boolean> and would choke on. It is set to zero: the icon comes from an automatic lookup, not from a choice, and that flag is what stops the desktop from ever replacing a chosen icon during a refresh. Base64 without line breaks. Delphi's encoder wraps at 76 characters by default, a wrapped data: URI does not display, and that trap has already been paid once on the containers.
This commit is contained in:
+82
-1
@@ -306,6 +306,8 @@ type
|
||||
/// Retire le code de deverrouillage : le cache se rescelle sous la seule
|
||||
/// cle de l'appareil. Exige un coffre ouvert.
|
||||
procedure ClearUnlockCode;
|
||||
/// Va chercher l'icone du site de l'entree ouverte, ou l'efface.
|
||||
procedure SetEntryIcon(AFetch: Boolean);
|
||||
procedure ReopenEntryByUuid(const AUuid: string);
|
||||
procedure OnClipTimer(Sender: TObject);
|
||||
procedure OnDeadline(Sender: TObject);
|
||||
@@ -330,7 +332,7 @@ implementation
|
||||
uses
|
||||
PMA.Crypto, PMA.SyncClient, PMA.Store, PMA.Biometric, PMA.Totp,
|
||||
PMA.ScreenGuard, PMA.Clipboard, PMA.Theme, PMA.WebUI, PMA.VaultPatch,
|
||||
PMA.SaveFile, System.JSON;
|
||||
PMA.SaveFile, PMA.Favicon, System.JSON;
|
||||
|
||||
{$R *.fmx}
|
||||
|
||||
@@ -1619,6 +1621,9 @@ begin
|
||||
TNetEncoding.URL.Decode(Param('k')),
|
||||
TNetEncoding.URL.Decode(Param('d')))
|
||||
|
||||
else if LAction = 'icon' then
|
||||
SetEntryIcon(Param('a') = 'fetch')
|
||||
|
||||
else if LAction = 'pinclear' then
|
||||
ClearUnlockCode
|
||||
|
||||
@@ -2545,6 +2550,82 @@ end;
|
||||
/// s'est laisse lire que sous le bon code. Sans cette condition, n'importe qui
|
||||
/// tenant l'appareil deverrouille pourrait retirer la protection sans la
|
||||
/// connaitre.
|
||||
/// L'ICONE DE L'ENTREE OUVERTE : la chercher, ou l'effacer.
|
||||
///
|
||||
/// LE SEUL APPEL DE CETTE APPLICATION QUI SORT AILLEURS QUE VERS LE DEPOT, et
|
||||
/// il ne part que sur demande, entree par entree. Il dit a DuckDuckGo qu'on
|
||||
/// s'interesse a ce domaine - c'est peu, mais ce n'est pas rien, et cela ne
|
||||
/// doit pas se produire dans le dos de quelqu'un.
|
||||
///
|
||||
/// La recherche est hors du thread principal : cinq secondes d'attente
|
||||
/// figeraient l'ecran. L'ecriture, elle, repasse par CommitChange - donc par
|
||||
/// le meme chemin que tout le reste, avec sa publication et son hors-ligne.
|
||||
procedure TForm8.SetEntryIcon(AFetch: Boolean);
|
||||
var
|
||||
LUuid, LSite, LTitle: string;
|
||||
begin
|
||||
if FBusy then Exit;
|
||||
if FCurrent.Uuid = '' then
|
||||
begin
|
||||
RefuseChange('This entry has no uuid');
|
||||
Exit;
|
||||
end;
|
||||
LUuid := FCurrent.Uuid;
|
||||
LTitle := FCurrent.Display;
|
||||
LSite := FCurrent.Site;
|
||||
|
||||
if not AFetch then
|
||||
begin
|
||||
CommitChange('Icon removed: ' + LTitle, 'icon removed from phone', LUuid,
|
||||
function (APlain: string): string
|
||||
begin
|
||||
Result := PatchEntryIcon(APlain, LUuid, '', False);
|
||||
end);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if HostOf(LSite) = '' then
|
||||
begin
|
||||
RefuseChange('This entry has no web address to look up');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
SetBusy(True);
|
||||
TThread.CreateAnonymousThread(
|
||||
procedure
|
||||
var
|
||||
LUri, LErr: string;
|
||||
begin
|
||||
LUri := '';
|
||||
LErr := '';
|
||||
try
|
||||
LUri := FetchFaviconDataUri(LSite, LErr);
|
||||
except
|
||||
on E: Exception do LErr := E.ClassName + ': ' + E.Message;
|
||||
end;
|
||||
TThread.Queue(nil,
|
||||
procedure
|
||||
begin
|
||||
SetBusy(False);
|
||||
if LUri = '' then
|
||||
begin
|
||||
Log('icon: ' + LErr);
|
||||
WebCall('PM.toast(' + JsArg('No icon - ' + LErr) + ')');
|
||||
Exit;
|
||||
end;
|
||||
Log(Format('icon: %d chars for %s', [Length(LUri), HostOf(LSite)]));
|
||||
// Provenance a FAUX : elle vient d'une recherche automatique, pas
|
||||
// d'un choix. Le poste ne remplace jamais une icone choisie lors
|
||||
// d'un rafraichissement, et ce drapeau est ce qui le lui dit.
|
||||
CommitChange('Icon set: ' + LTitle, 'icon fetched from phone', LUuid,
|
||||
function (APlain: string): string
|
||||
begin
|
||||
Result := PatchEntryIcon(APlain, LUuid, LUri, False);
|
||||
end);
|
||||
end);
|
||||
end).Start;
|
||||
end;
|
||||
|
||||
procedure TForm8.ClearUnlockCode;
|
||||
begin
|
||||
if FBusy then Exit;
|
||||
|
||||
Reference in New Issue
Block a user