Skip to content

Commit

Permalink
fix(OPDS): download content-disposition UTF8 percent-escaped filename…
Browse files Browse the repository at this point in the history
…, www-authenticate response error description
  • Loading branch information
danielweck committed Jan 13, 2025
1 parent 42d136a commit 43df15e
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 4 deletions.
17 changes: 16 additions & 1 deletion src/main/redux/sagas/api/browser/browse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,22 @@ export function* browse(urlRaw: string): SagaGenerator<THttpGetBrowserResultView

// Failed :

ok(data.isSuccess, `message: ${statusMessage} | url: ${baseUrl} | type: ${_contentType} | code: ${+isFailure}${+isNetworkError}${+isAbort}${+isTimeout}`);
if (!data.isSuccess) {
// example:
// 'Bearer error="insufficient_access", error_description="The user represented by the token is not allowed to perform the requested action.", error_uri="https://documentation.openiddict.com/errors/ID2095"'
data.response?.headers.forEach((value, key) => {
console.log(`HTTP RESPONSE HEADER '${key}' ==> '${value}'`);

This comment has been minimized.

Copy link
@panaC

panaC Jan 13, 2025

Member

console.log instead debug

why ?

This comment has been minimized.

Copy link
@danielweck

danielweck Jan 13, 2025

Author Member

my bad, let me fix this :)
(thanks!)

});
const wwwAuthenticate = data.response?.headers.get("WWW-Authenticate");
if (wwwAuthenticate) {
console.log("www-authenticate:", data.response?.headers.get("WWW-Authenticate")); // case-insensitve (actual "www-authenticate")
if (wwwAuthenticate.startsWith("Bearer") && wwwAuthenticate.includes("error=")) {
throw new Error(`www-authenticate ERROR: ${data.statusCode}/${statusMessage} -- ${wwwAuthenticate} (${baseUrl})`);
}
}
}

ok(data.isSuccess, `message: ${data.statusCode}/${statusMessage} | url: ${baseUrl} | type: ${_contentType} | code: ${+isFailure}${+isNetworkError}${+isAbort}${+isTimeout}`);

debug(`unknown url content-type : ${baseUrl} - ${contentType}`);
throw new Error(
Expand Down
19 changes: 16 additions & 3 deletions src/main/redux/sagas/downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ function* downloadLinkRequest(linkHref: string, controller: AbortController): Sa
);

const opdsAuthChannel = getOpdsAuthenticationChannel();

debug("put the authentication model in the saga authChannel", JSON.stringify(r2OpdsAuth, null, 4));
opdsAuthChannel.put([r2OpdsAuth, linkHref]);

Expand Down Expand Up @@ -337,16 +337,27 @@ function downloadCreateFilename(contentType: string | undefined, contentDisposit
}
}

// example
// "attachment; filename=xxx.epub; filename*=UTF-8''xxx.epub"
let contentDispositionFilename = "";
if (contentDisposition) {
const res = /filename=(\"(.*)\"|(.*))/g.exec(contentDisposition);
const res = /filename=(\"([^;]+)\"|([^;]+))/.exec(contentDisposition);
const filenameInCD = res ? res[2] || res[3] || "" : "";
if (acceptedExtension(path.extname(filenameInCD))) {
contentDispositionFilename = filenameInCD;
debug(`contentDispositionFilename: ${contentDispositionFilename}`);
}
}
if (contentDisposition && !contentDispositionFilename) {
const res = /filename\*=UTF-8''(\"([^;]+)\"|([^;]+))/.exec(contentDisposition);
const filenameInCD = decodeURIComponent(res ? res[2] || res[3] || "" : "");
if (acceptedExtension(path.extname(filenameInCD))) {
contentDispositionFilename = filenameInCD;
debug(`contentDispositionFilename UTF8: ${contentDispositionFilename}`);
}
}

if (contentDispositionFilename && contentDispositionFilename &&
if (contentDispositionFilename &&
path.extname(contentDispositionFilename).toLowerCase() === path.extname(contentTypeFilename).toLowerCase()
) {
debug("contentType and contentDisposition have the same extension ! Good catch !", contentTypeFilename, contentDispositionFilename);
Expand Down Expand Up @@ -387,6 +398,8 @@ function downloadReadStreamProgression(readStream: NodeJS.ReadableStream, conten
const iv = setInterval(() => {

speed = downloadedSpeed / 1024;
// contentLength can be zero (unfortunately), pct is Infinity :(
// debug("downloadedLength: ", downloadedLength, "contentLength: ", contentLength);
pct = Math.ceil(downloadedLength / contentLength * 100);
debug("speed: ", speed, "kb/s", "pct: ", pct, "%");

Expand Down
6 changes: 6 additions & 0 deletions src/main/services/opds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export class OpdsService {
}

public async opdsRequestTransformer(httpGetData: IHttpGetResult<IOpdsResultView>): Promise<IOpdsResultView | undefined> {
// httpGetData.response?.headers.forEach((value, key) => {
// console.log(`HTTP RESPONSE HEADER '${key}' ==> '${value}'`);
// });
// console.log("www-authenticate:", httpGetData.response?.headers.get("WWW-Authenticate")); // case-insensitve (actual "www-authenticate")
// example:
// 'Bearer error="insufficient_access", error_description="The user represented by the token is not allowed to perform the requested action.", error_uri="https://documentation.openiddict.com/errors/ID2095"'

const {
url: _baseUrl,
Expand Down

0 comments on commit 43df15e

Please sign in to comment.