mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: show inline images whose part name is a path
This commit is contained in:
@@ -69,6 +69,15 @@ describe('blob operations', () => {
|
||||
|
||||
expect(url).toContain('my%20file%20(1).pdf');
|
||||
});
|
||||
|
||||
// A part named like a path: an encoded slash (%2F) in the URL path is
|
||||
// refused or decoded by common reverse proxies, so inline images went
|
||||
// blank (webmail #1371).
|
||||
it('keeps only the last path component of a part name', () => {
|
||||
expect(getDownloadUrl('b1', '/images/banner.jpg', 'image/jpeg')).toContain('/b1/banner.jpg?');
|
||||
expect(getDownloadUrl('b1', 'C:\\pics\\logo.png', 'image/png')).toContain('/b1/logo.png?');
|
||||
expect(getDownloadUrl('b1', 'dir/', 'image/png')).toContain('/b1/download?');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -194,6 +194,8 @@ describe('JMAPClient hardening', () => {
|
||||
const session = await client.connect('https://mail.example.com', 'user', 'pass');
|
||||
expect(session.apiUrl).toBe('https://mail.example.com/jmap/');
|
||||
expect(client.getBlobDownloadUrl('b1', 'n')).toBe('https://mail.example.com/download/acc-1/b1/n?type=application%2Foctet-stream');
|
||||
// A part name that is a path keeps its last component (webmail #1371).
|
||||
expect(client.getBlobDownloadUrl('b1', '/images/n.jpg')).toBe('https://mail.example.com/download/acc-1/b1/n.jpg?type=application%2Foctet-stream');
|
||||
});
|
||||
|
||||
it('refetches the final URL when a redirect dropped the Authorization header', async () => {
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ import { File } from 'expo-file-system';
|
||||
import type * as LegacyFileSystemTypes from 'expo-file-system/legacy';
|
||||
import { jmapClient } from './jmap-client';
|
||||
import { batched } from './jmap-result';
|
||||
import { downloadNameSegment } from './download-name';
|
||||
import { CAPABILITIES } from './types';
|
||||
import { getClientCertAlias, secureFetch } from '../lib/client-cert';
|
||||
|
||||
@@ -268,7 +269,7 @@ export function getDownloadUrl(
|
||||
return session.downloadUrl
|
||||
.replace('{accountId}', encodeURIComponent(accountId ?? jmapClient.accountId))
|
||||
.replace('{blobId}', encodeURIComponent(blobId))
|
||||
.replace('{name}', encodeURIComponent(name ?? 'download'))
|
||||
.replace('{name}', encodeURIComponent(downloadNameSegment(name)))
|
||||
.replace('{type}', encodeURIComponent(type ?? 'application/octet-stream'));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* The {name} segment of a JMAP download URL (RFC 8620 §6.2). Port of the
|
||||
* webmail's lib/jmap/download-name.ts (#1371).
|
||||
*
|
||||
* The server only uses it for the Content-Disposition filename, but mailers
|
||||
* sometimes put a path in a part's name ("/images/banner.jpg"). Percent-
|
||||
* encoded, its slashes become %2F, which Apache (without AllowEncodedSlashes
|
||||
* NoDecode), Traefik and nginx proxy_pass with a URI reject or decode, so the
|
||||
* download failed and inline cid: images showed as blank. Keep the last path
|
||||
* component instead.
|
||||
*/
|
||||
export function downloadNameSegment(name: string | undefined): string {
|
||||
const base = (name ?? '').split(/[/\\]/).pop()?.trim();
|
||||
return base || 'download';
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type OAuthTokenSource,
|
||||
} from '../lib/oauth';
|
||||
import { FirstTouchGate } from './first-touch-gate';
|
||||
import { downloadNameSegment } from './download-name';
|
||||
import { writeSharedSecret, moveToSharedGroup } from '../lib/ios-push-shared';
|
||||
import { beginOwnWrite, recordOwnEmailWrites } from './own-writes';
|
||||
|
||||
@@ -1347,7 +1348,7 @@ export class JMAPClient {
|
||||
return this.session.downloadUrl
|
||||
.replace('{accountId}', encodeURIComponent(accountId ?? this.accountId))
|
||||
.replace('{blobId}', encodeURIComponent(blobId))
|
||||
.replace('{name}', encodeURIComponent(name || 'download'))
|
||||
.replace('{name}', encodeURIComponent(downloadNameSegment(name)))
|
||||
.replace('{type}', encodeURIComponent(type || 'application/octet-stream'));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user