diff --git a/books/views.py b/books/views.py index 44b6e85..c1322ae 100644 --- a/books/views.py +++ b/books/views.py @@ -111,15 +111,22 @@ def replace_book_data(request, pk): except (json.JSONDecodeError, ValueError): return JsonResponse({'error': 'invalid JSON'}, status=400) - data_ct = body.get('data_ct', '') - data_iv = body.get('data_iv', '') + data_ct = body.get('data_ct', '') + data_iv = body.get('data_iv', '') + meta_ct = body.get('meta_ct', '') + meta_iv = body.get('meta_iv', '') if not all([data_ct, data_iv]): return JsonResponse({'error': 'data_ct and data_iv required'}, status=400) + update_fields = ['data_ct', 'data_iv'] book.data_ct = data_ct book.data_iv = data_iv - book.save(update_fields=['data_ct', 'data_iv']) + if meta_ct and meta_iv: + book.meta_ct = meta_ct + book.meta_iv = meta_iv + update_fields += ['meta_ct', 'meta_iv'] + book.save(update_fields=update_fields) return JsonResponse({'ok': True}) diff --git a/static/js/app.js b/static/js/app.js index 6ca1436..581cda1 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2879,11 +2879,43 @@ function repairBook(bookId) { try { const key = await getOrCreateEncKey(); const buf = await file.arrayBuffer(); - const enc = await encryptBytes(key, buf); + const isPdf = /\.pdf$/i.test(file.name); + const type = isPdf ? 'pdf' : 'epub'; + + let title = file.name.replace(/\.(epub|pdf)$/i, ''), author = ''; + try { + if (isPdf) { + const pdfDoc = await pdfjsLib.getDocument({data: new Uint8Array(buf.slice(0))}).promise; + const meta = await pdfDoc.getMetadata(); + title = meta.info?.Title?.trim() || title; + author = meta.info?.Author?.trim() || ''; + } else { + const zip = await JSZip.loadAsync(buf.slice(0)); + const containerXml = await zip.file('META-INF/container.xml').async('text'); + const opfPath = new DOMParser() + .parseFromString(containerXml, 'application/xml') + .querySelector('rootfile')?.getAttribute('full-path'); + if (opfPath) { + const opfDoc = new DOMParser().parseFromString( + await zip.file(opfPath).async('text'), 'application/xml'); + title = opfDoc.querySelector('metadata > title, metadata > *|title')?.textContent?.trim() || title; + author = opfDoc.querySelector('metadata > creator, metadata > *|creator')?.textContent?.trim() || ''; + } + } + } catch (e) { /* keep filename as title */ } + + const metaJson = new TextEncoder().encode(JSON.stringify({title, author, filename: file.name, type})); + const [metaEnc, dataEnc] = await Promise.all([ + encryptBytes(key, metaJson), + encryptBytes(key, buf), + ]); const res = await fetch(`/books/${bookId}/replace-data/`, { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken()}, - body: JSON.stringify({data_ct: enc.ciphertext, data_iv: enc.iv}), + body: JSON.stringify({ + data_ct: dataEnc.ciphertext, data_iv: dataEnc.iv, + meta_ct: metaEnc.ciphertext, meta_iv: metaEnc.iv, + }), }); if (!(await res.json()).ok) throw new Error('Server error'); await _evictCachedBook(bookId);