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
{ {
@@ -96,6 +96,21 @@
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>
<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>
<h3>Parameters</h3> <h3>Parameters</h3>
<table> <table>
<tr> <tr>
@@ -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 != "" {