worker-pastebin/src/web/fileDecrypter.coffee

131 lines
3.8 KiB
CoffeeScript

import React from "react"
import { Link } from "react-router-dom"
import * as crypto from "../crypto"
import * as util from "../util"
class FileDecrypter extends React.Component
constructor: (props) ->
super props
@originalUrl = "/paste/#{props.id}?original"
# We simply let it fail if there's no key / iv provided in window.location.hash
# also we don't care about decryption failure. Just let it look like a broken page
# if someone tries to brute-force
[key, iv] = window.location.hash.replace("#", "").split '+'
@state =
name: null
mime: null
length: null
downloading: false
decrypting: false
progress: 0
key: key
iv: iv
downloaded: null
componentDidMount: ->
# Fetch metadata to show to user
# We can use fetch API here
resp = await fetch @originalUrl
# Fail silently as explained above
return if not resp.ok
mime = resp.headers.get 'content-type'
[_, name] = resp.headers.get 'content-disposition'
.split 'filename*='
[name, mime] = await crypto.decryptMetadata @state.key, @state.iv, name, mime
@setState
name: name
mime: mime
length: parseInt resp.headers.get 'content-length'
componentWillUnmount: ->
if @state.downloaded
URL.revokeObjectURL @state.downloaded
downloadFile: =>
@setState
downloading: true
decrypting: false
progress: 0
# For progress, we have to use XHR
xhr = new XMLHttpRequest()
xhr.responseType = "arraybuffer"
xhr.addEventListener 'progress', (e) =>
if e.lengthComputable
@setState
progress: e.loaded / e.total
xhr.addEventListener 'readystatechange', =>
if xhr.readyState == XMLHttpRequest.DONE
if xhr.status == 200
await @decryptFile xhr.response
@setState
downloading: false
xhr.open 'GET', @originalUrl
xhr.send()
decryptFile: (file) =>
@setState
decrypting: true
decrypted = await crypto.decryptFile @state.key, @state.iv, file
blob = new Blob [decrypted],
type: @state.mime
@setState
decrypting: false
blob: blob
downloaded: URL.createObjectURL blob
render: ->
<div className="content-pastebin">{
if not @state.name
<p>Loading...</p>
else
<div className="content-file-info">
<p>{@state.name}</p>
<p>{@state.mime}</p>
<p>{util.humanFileSize @state.length}</p>
{
if not @state.downloaded
<button
className="button-blue"
disabled={@state.downloading}
onClick={@downloadFile}
>{
if not @state.downloading
"Download"
else if @state.decrypting
"Decrypting"
else
util.progressText @state.progress
}</button>
else
# Use an actual link here instead of triggering click
# on a hidden link, because on some browsers it doesn't work
<a
className="button-blue"
href={@state.downloaded}
download={@state.name}
>
Save File
</a>
}{
# In-browser previewing for certain file types
# we can't just use this for all because it cannot handle file name
@state.downloaded and util.shouldShowInline(@state.mime) and
<a
className="button-blue"
href={@state.downloaded}
target="_blank"
>
Preview
</a>
}
<br/>
<Link
className="button-blue"
to="/paste/text"
>
Home
</Link>
</div>
}</div>
export default FileDecrypter