Merge branch 'master' of fornaxian.com:Pixeldrain/pixeldrain-web

This commit is contained in:
2020-02-11 13:58:27 +01:00
13 changed files with 150 additions and 92 deletions

View File

@@ -17,7 +17,7 @@ var client = &http.Client{Timeout: time.Minute * 5}
// PixelAPI is the Pixeldrain API client // PixelAPI is the Pixeldrain API client
type PixelAPI struct { type PixelAPI struct {
apiEndpoint string apiEndpoint string
apiKey string APIKey string
RealIP string RealIP string
} }
@@ -60,8 +60,8 @@ func (p *PixelAPI) jsonRequest(method, url string, target interface{}) error {
Message: err.Error(), Message: err.Error(),
} }
} }
if p.apiKey != "" { if p.APIKey != "" {
req.SetBasicAuth("", p.apiKey) req.SetBasicAuth("", p.APIKey)
} }
if p.RealIP != "" { if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP) req.Header.Set("X-Real-IP", p.RealIP)
@@ -86,8 +86,8 @@ func (p *PixelAPI) getString(url string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if p.apiKey != "" { if p.APIKey != "" {
req.SetBasicAuth("", p.apiKey) req.SetBasicAuth("", p.APIKey)
} }
if p.RealIP != "" { if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP) req.Header.Set("X-Real-IP", p.RealIP)
@@ -110,8 +110,8 @@ func (p *PixelAPI) getRaw(url string) (io.ReadCloser, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if p.apiKey != "" { if p.APIKey != "" {
req.SetBasicAuth("", p.apiKey) req.SetBasicAuth("", p.APIKey)
} }
if p.RealIP != "" { if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP) req.Header.Set("X-Real-IP", p.RealIP)
@@ -141,8 +141,8 @@ func (p *PixelAPI) form(
Message: err.Error(), Message: err.Error(),
} }
} }
if p.apiKey != "" { if p.APIKey != "" {
req.SetBasicAuth("", p.apiKey) req.SetBasicAuth("", p.APIKey)
} }
if p.RealIP != "" { if p.RealIP != "" {
req.Header.Set("X-Real-IP", p.RealIP) req.Header.Set("X-Real-IP", p.RealIP)

View File

@@ -58,7 +58,7 @@ func (p *PixelAPI) UserLogin(username, password string, saveKey bool) (resp *Log
return nil, err return nil, err
} }
if saveKey { if saveKey {
p.apiKey = resp.APIKey p.APIKey = resp.APIKey
} }
return resp, nil return resp, nil
} }

View File

@@ -1,7 +1,7 @@
function DetailsWindow(viewer) { function DetailsWindow(viewer) {
this.viewer = viewer this.viewer = viewer
this.visible = false this.visible = false
this.fileID = "" this.file = null
this.graph = 0 this.graph = 0
this.divPopup = document.getElementById("details_popup") this.divPopup = document.getElementById("details_popup")
@@ -32,16 +32,16 @@ DetailsWindow.prototype.toggle = function() {
if (this.graph === 0) { if (this.graph === 0) {
this.renderGraph() this.renderGraph()
} }
this.updateGraph(this.fileID) this.updateGraph(this.file)
} }
} }
DetailsWindow.prototype.setDetails = function(file) { DetailsWindow.prototype.setFile = function(file) {
this.file = file
let desc = "" let desc = ""
if (this.viewer.isList) { if (this.viewer.isList) {
desc = file.description desc = file.description
} }
this.fileID = file.id
this.divFileDetails.innerHTML = "<table>" this.divFileDetails.innerHTML = "<table>"
+ "<tr><td>Name<td><td>" + escapeHTML(file.name) + "</td></tr>" + "<tr><td>Name<td><td>" + escapeHTML(file.name) + "</td></tr>"
+ "<tr><td>URL<td><td><a href=\""+file.link+"\">"+file.link+"</a></td></tr>" + "<tr><td>URL<td><td><a href=\""+file.link+"\">"+file.link+"</a></td></tr>"

View File

@@ -93,7 +93,7 @@ Toolbar.prototype.download = function() {
return return
} }
fetch(this.currentFile.file_availability_href).then(resp => { fetch(this.currentFile.availability_href).then(resp => {
return resp.json() return resp.json()
}).then(resp => { }).then(resp => {
let popupDiv = document.getElementById("captcha_popup") let popupDiv = document.getElementById("captcha_popup")

View File

@@ -54,8 +54,8 @@ function Viewer(type, viewToken, data) {
this.setFile(fileFromSkyNet(data)) this.setFile(fileFromSkyNet(data))
} }
this.renderSponsors() // this.renderSponsors()
window.addEventListener("resize", e => { this.renderSponsors(e) }) // window.addEventListener("resize", e => { this.renderSponsors(e) })
// Register keyboard shortcuts // Register keyboard shortcuts
document.addEventListener("keydown", e => { this.keyboardEvent(e) }) document.addEventListener("keydown", e => { this.keyboardEvent(e) })
@@ -74,8 +74,8 @@ Viewer.prototype.setFile = function(file) {
document.title = file.name + " ~ pixeldrain" document.title = file.name + " ~ pixeldrain"
} }
// Update the file details // Relay the file change event to all components
this.detailsWindow.setDetails(file) this.detailsWindow.setFile(file)
this.toolbar.setFile(file) this.toolbar.setFile(file)
// Register a new view. We don't care what this returns becasue we can't // Register a new view. We don't care what this returns becasue we can't
@@ -129,40 +129,40 @@ Viewer.prototype.setFile = function(file) {
} }
} }
Viewer.prototype.renderSponsors = function() { // Viewer.prototype.renderSponsors = function() {
let scale = 1 // let scale = 1
let scaleWidth = 1 // let scaleWidth = 1
let scaleHeight = 1 // let scaleHeight = 1
let minWidth = 728 // let minWidth = 728
let minHeight = 800 // let minHeight = 800
if (window.innerWidth < minWidth) { // if (window.innerWidth < minWidth) {
scaleWidth = window.innerWidth/minWidth // scaleWidth = window.innerWidth/minWidth
} // }
if (window.innerHeight < minHeight) { // if (window.innerHeight < minHeight) {
scaleHeight = window.innerHeight/minHeight // scaleHeight = window.innerHeight/minHeight
} // }
scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight // scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight
// Because of the scale transformation the automatic margins don't work // // Because of the scale transformation the automatic margins don't work
// anymore. So we have to maunally calculate the margin. Where we take the // // anymore. So we have to maunally calculate the margin. Where we take the
// width of the viewport - the width of the ad to calculate the amount of // // width of the viewport - the width of the ad to calculate the amount of
// pixels around the ad. We multiply the ad size by the scale we calcualted // // pixels around the ad. We multiply the ad size by the scale we calcualted
// to account for the smaller size. // // to account for the smaller size.
let offset = (window.innerWidth - (minWidth*scale)) / 2 // let offset = (window.innerWidth - (minWidth*scale)) / 2
if (offset < 0) { // if (offset < 0) {
offset = 0 // offset = 0
} // }
document.querySelector(".sponsors > iframe").style.marginLeft = offset+"px" // document.querySelector(".sponsors > iframe").style.marginLeft = offset+"px"
if (scale == 1) { // if (scale == 1) {
document.querySelector(".sponsors > iframe").style.transform = "none" // document.querySelector(".sponsors > iframe").style.transform = "none"
document.querySelector(".sponsors").style.height = "90px" // document.querySelector(".sponsors").style.height = "90px"
} else { // } else {
document.querySelector(".sponsors > iframe").style.transform = "scale("+scale+")" // document.querySelector(".sponsors > iframe").style.transform = "scale("+scale+")"
document.querySelector(".sponsors").style.height = (scale*90)+"px" // document.querySelector(".sponsors").style.height = (scale*90)+"px"
} // }
} // }
Viewer.prototype.keyboardEvent = function(evt) { Viewer.prototype.keyboardEvent = function(evt) {
if (evt.ctrlKey || evt.altKey) { if (evt.ctrlKey || evt.altKey) {
@@ -243,8 +243,8 @@ function fileFromAPIResp(resp) {
function fileFromSkyNet(resp) { function fileFromSkyNet(resp) {
let file = fileFromAPIResp(resp) let file = fileFromAPIResp(resp)
file.icon_href = "/res/img/mime/empty.png" file.icon_href = "/res/img/mime/empty.png"
file.get_href = "https://siasky.net/"+resp.id file.get_href = "https://sky.pixeldrain.com/"+resp.id
file.download_href = "https://siasky.net/"+resp.id+"?attachment=1" file.download_href = "https://sky.pixeldrain.com/"+resp.id+"?attachment=1"
file.availability_href = "" file.availability_href = ""
file.view_href = "" file.view_href = ""
file.timeseries_href = "" file.timeseries_href = ""

View File

@@ -461,6 +461,8 @@ input[type=file]{
background-color: var(--scrollbar_foreground_color); background-color: var(--scrollbar_foreground_color);
border-radius: 10px; border-radius: 10px;
border: 5px solid var(--scrollbar_background_color); border: 5px solid var(--scrollbar_background_color);
height: 40px;
width: 40px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background-color: var(--scrollbar_hover_color); background-color: var(--scrollbar_hover_color);

View File

@@ -95,15 +95,6 @@
<div class="center" style="width: 100px; height: 100px;">{{template "spinner.svg" .}}</div> <div class="center" style="width: 100px; height: 100px;">{{template "spinner.svg" .}}</div>
</div> </div>
</div> </div>
<div id="sponsors" class="sponsors">
<!-- scrolling="no" is not allowed by the W3C, but overflow: hidden doesn't work in chrome, so I have no choice -->
<iframe
data-aa="73974"
src="//ad.a-ads.com/73974?size=728x90&background_color={{.Style.Layer1Color.RGB}}&text_color={{.Style.TextColor.RGB}}&title_color={{.Style.HighlightColor.RGB}}&title_hover_color={{.Style.HighlightColor.RGB}}&link_color={{.Style.HighlightColor.RGB}}&link_hover_color={{.Style.HighlightColor.RGB}}"
style="width:728px; height:90px; border:none; padding:0; overflow:hidden;"
scrolling="no">
</iframe>
</div>
<!-- Popup windows, hidden by default --> <!-- Popup windows, hidden by default -->
<div id="details_popup" class="popup details_popup"> <div id="details_popup" class="popup details_popup">

View File

@@ -65,7 +65,7 @@
{ {
"success": false, "success": false,
"value": "file_too_large", "value": "file_too_large",
"message": "The file you tried to upload is too large. Max 5000 MB allowed." "message": "The file you tried to upload is too large"
}</pre> }</pre>
<pre>HTTP 500: Internal Server Error <pre>HTTP 500: Internal Server Error
{ {
@@ -92,9 +92,24 @@
byte range requests. byte range requests.
</p> </p>
<p> <p>
When '?download' is added to the URL the server will send an When '?download' is added to the URL the server will send an
attachment header instead of inline rendering, which causes the attachment header instead of inline rendering, which causes the
browser to show a 'Save File' dialog. browser to show a 'Save File' dialog.
</p>
<p>
Warning: If a file is using too much bandwidth it can be rate
limited. The rate limit will be enabled if a file has ten times more
downloads than views. The owner of a file can always download it.
When a file is rate limited the user will need to fill out a captcha
in order to continue downloading the file. The captcha will only
appear on the file viewer page (pixeldrain.com/u/{id}). Rate
limiting has been added to prevent the spread of viruses and to stop
direct linking.
</p>
<p>
Pixeldrain also includes a virus scanner. If a virus has been
detected in a file the user will also have to fill in a captcha to
download it.
</p> </p>
<h3>Parameters</h3> <h3>Parameters</h3>
<table> <table>
@@ -118,7 +133,31 @@
</tr> </tr>
</table> </table>
<h3>Returns</h3> <h3>Returns</h3>
A file output stream. <pre>HTTP 200: OK
The requested file.
</pre>
<pre>HTTP 404: Not Found
{
"success": false,
"value": "not_found",
"message": "The entity you requested could not be found"
}
</pre>
<pre>HTTP 403: Forbidden
{
"success": false,
"value": "file_rate_limited_captcha_required",
"message": "This file is using too much bandwidth. For anonymous downloads a captcha is required now. The captcha entry is available on the download page"
}
</pre>
<pre>HTTP 403: Forbidden
{
"success": false,
"value": "virus_detected_captcha_required",
"message": "This file has been marked as malware by our scanning systems. To avoid infecting other systems through automated downloads we require you to enter a captcha. The captcha entry is available on the download page"
}
</pre>
</div> </div>
</details> </details>
{{end}} {{end}}
@@ -152,17 +191,15 @@
<pre>HTTP 200: OK <pre>HTTP 200: OK
{ {
"success": true, "success": true,
"id": "123abc", "id": "1234abcd",
"name": "screenshot.png", "name": "screenshot.png",
"date_upload": 1485894987, // Timestamp "date_upload": 2020-02-04T18:34:05.706801Z,
"date_last_view": 1485894987, // Timestamp "date_last_view": 2020-02-04T18:34:05.706801Z,
"size": 5694837, // Bytes "size": 5694837, // Bytes
"views" 1234, // Amount of unique file views "views" 1234, // Amount of unique file views
"bandwidth_used": 1234567890, // Bytes "bandwidth_used": 1234567890, // Bytes
"mime_type" "image/png", "mime_type" "image/png",
"description": "File description", "thumbnail_href": "/file/1234abcd/thumbnail" // Link to a thumbnail of this file
"mime_image": "http://pixeldra.in/res/img/mime/image-png.png", // Image associated with the mime type
"thumbnail": "http://pixeldra.in/api/thumbnail/123abc" // Link to a thumbnail of this file
}</pre> }</pre>
<pre>HTTP 404: Not Found <pre>HTTP 404: Not Found
{ {

View File

@@ -121,37 +121,50 @@
"success": true, "success": true,
"id": "L8bhwx", "id": "L8bhwx",
"title": "Rust in Peace", "title": "Rust in Peace",
"date_created": 1513033315, "date_created": 2020-02-04T18:34:13.466276Z,
"files": [ "files": [
// These structures are the same as the file info response, except for the detail_href and description fields
{ {
"detail_href": "/file/_SqVWi/info", "detail_href": "/file/_SqVWi/info",
"description": "",
"success": true,
"id": "_SqVWi", "id": "_SqVWi",
"name": "01 Holy Wars... The Punishment Due.mp3", "name": "01 Holy Wars... The Punishment Due.mp3",
"description": "", "size": 123456,
"date_created": 1513033304, "date_created": 2020-02-04T18:34:13.466276Z,
"date_last_view": 1513033304, "date_last_view": 2020-02-04T18:34:13.466276Z,
"mime_type": "audio/mp3",
"views": 1, "views": 1,
"bandwidth_used": 1234567890 "bandwidth_used": 1234567890,
"thumbnail_href": "/file/_SqVWi/thumbnail"
}, },
{ {
"detail_href": "/file/RKwgZb/info", "detail_href": "/file/RKwgZb/info",
"description": "",
"success": true,
"id": "RKwgZb", "id": "RKwgZb",
"name": "02 Hangar 18.mp3", "name": "02 Hangar 18.mp3",
"description": "", "size": 123456,
"date_created": 1513033304, "date_created": 2020-02-04T18:34:13.466276Z,
"date_last_view": 1513033304, "date_last_view": 2020-02-04T18:34:13.466276Z,
"mime_type": "audio/mp3",
"views": 2, "views": 2,
"bandwidth_used": 1234567890 "bandwidth_used": 1234567890,
"thumbnail_href": "/file/RKwgZb/thumbnail"
}, },
{ {
"detail_href": "/file/DRaL_e/info", "detail_href": "/file/DRaL_e/info",
"description": "",
"success": true,
"id": "DRaL_e", "id": "DRaL_e",
"name": "03 Take No Prisoners.mp3", "name": "03 Take No Prisoners.mp3",
"description": "", "size": 123456,
"date_created": 1513033304, "date_created": 2020-02-04T18:34:13.466276Z,
"date_last_view": 1513033304, "date_last_view": 2020-02-04T18:34:13.466276Z,
"mime_type": "audio/mp3",
"views": 3, "views": 3,
"bandwidth_used": 1234567890 "bandwidth_used": 1234567890,
"thumbnail_href": "/file/DRaL_e/thumbnail"
} }
] ]
} }

View File

@@ -165,7 +165,7 @@ func (wc *WebController) serveSkynetViewer(w http.ResponseWriter, r *http.Reques
// Get the first few bytes from the file to probe the content type and // Get the first few bytes from the file to probe the content type and
// length // length
rq, err := http.NewRequest("GET", "https://siasky.net/"+p.ByName("id"), nil) rq, err := http.NewRequest("GET", "https://sky.pixeldrain.com/"+p.ByName("id"), nil)
if err != nil { if err != nil {
log.Warn("Failed to make request to sia portal: %s", err) log.Warn("Failed to make request to sia portal: %s", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)

View File

@@ -64,7 +64,10 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request)
log.Debug("Session check for key '%s' failed: %s", key, err) log.Debug("Session check for key '%s' failed: %s", key, err)
if err.Error() == "authentication_required" || err.Error() == "authentication_failed" { if err.Error() == "authentication_required" || err.Error() == "authentication_failed" {
// This key is invalid, delete it // Disable API authentication
t.PixelAPI.APIKey = ""
// Remove the authentication cookie
log.Debug("Deleting invalid API key") log.Debug("Deleting invalid API key")
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "pd_auth_key", Name: "pd_auth_key",
@@ -73,6 +76,13 @@ func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request)
Expires: time.Unix(0, 0), Expires: time.Unix(0, 0),
Domain: wc.sessionCookieDomain, Domain: wc.sessionCookieDomain,
}) })
http.SetCookie(w, &http.Cookie{
Name: "pd_auth_key",
Value: "",
Path: "/",
Expires: time.Unix(0, 0),
Domain: ".pixeldrain.com",
})
} }
return t return t
} }

View File

@@ -172,12 +172,16 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f Form) {
// Request was a success // Request was a success
f.SubmitSuccess = true f.SubmitSuccess = true
f.SubmitMessages = []template.HTML{"Success!"} f.SubmitMessages = []template.HTML{"Success!"}
// Set the autentication cookie
f.Extra.SetCookie = &http.Cookie{ f.Extra.SetCookie = &http.Cookie{
Name: "pd_auth_key", Name: "pd_auth_key",
Value: loginResp.APIKey, Value: loginResp.APIKey,
Path: "/", Path: "/",
Expires: time.Now().AddDate(50, 0, 0), Expires: time.Now().AddDate(50, 0, 0),
Domain: wc.sessionCookieDomain, Domain: wc.sessionCookieDomain,
SameSite: http.SameSiteStrictMode,
Secure: true,
} }
f.Extra.RedirectTo = "/user" f.Extra.RedirectTo = "/user"
} }

View File

@@ -183,6 +183,7 @@ func (wc *WebController) serveForm(
// Execute the extra actions if any // Execute the extra actions if any
if td.Form.Extra.SetCookie != nil { if td.Form.Extra.SetCookie != nil {
w.Header().Del("Set-Cookie")
http.SetCookie(w, td.Form.Extra.SetCookie) http.SetCookie(w, td.Form.Extra.SetCookie)
} }
if td.Form.Extra.RedirectTo != "" { if td.Form.Extra.RedirectTo != "" {