diff --git a/go.mod b/go.mod index 1e4bf74..aa9cc8d 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,6 @@ require ( github.com/Fornaxian/pd_mime_type v0.0.0-20200204165508-2815edf3a145 github.com/google/uuid v1.1.1 github.com/julienschmidt/httprouter v1.3.0 - github.com/microcosm-cc/bluemonday v1.0.3 - github.com/russross/blackfriday/v2 v2.0.1 + github.com/microcosm-cc/bluemonday v1.0.4 + github.com/russross/blackfriday/v2 v2.1.0 ) diff --git a/res/include/md/acknowledgements.md b/res/include/md/acknowledgements.md new file mode 100644 index 0000000..5ce66b4 --- /dev/null +++ b/res/include/md/acknowledgements.md @@ -0,0 +1,43 @@ +# Acknowledgements + +## Software used + + * [Go](https://golang.org/) + * [ScyllaDB](https://www.scylladb.com/) + * [CockroachDB](https://www.cockroachlabs.com/) + * [Nginx](https://www.nginx.com/) + * [Ubuntu Server edition](https://ubuntu.com/) + * [Debian](https://www.debian.org/) + +## Programming libraries + + * [scylladb/gocql](https://github.com/scylladb/gocql) + * [scylladb/gocqlx](https://github.com/scylladb/gocqlx) + * [lib/pq](github.com/lib/pq) + * [jmoiron/sqlx](https://github.com/jmoiron/sqlx) + * [BurntSushi/toml](https://github.com/BurntSushi/toml) + * [julienschmidt/httprouter](https://github.com/julienschmidt/httprouter) + * [gabriel-vasile/mimetype](https://github.com/gabriel-vasile/mimetype) + * [disintegration/imaging](github.com/disintegration/imaging) + * [gorilla/websocket](github.com/gorilla/websocket) + * [shopspring/decimal](github.com/shopspring/decimal) + * [jhillyerd/enmime](github.com/jhillyerd/enmime) + * [russross/blackfriday](https://github.com/russross/blackfriday) + * [microcosm-cc/bluemonday](github.com/microcosm-cc/bluemonday) + +### Web framework + + * [Svelte](https://svelte.dev/) + +## Security work + + * 2020-12-06 Security researcher Arian Firoozfar reported a cross-site + scripting vulnerability on the file viewer page. The issue was fixed the + following day. + + * 2017-12-04 Security researcher Hangyi reported a cross-site scripting + vulnerability on the file viewer page. The issue was fixed on the 6th. + +If you have discovered a security issue in pixeldrain please disclose it +responsibly at [support@pixeldrain.com](mailto:support@pixeldrain.com). We do +not have a bug bounty program. diff --git a/res/include/script/admin.js b/res/include/script/admin.js index aec966a..bc96afa 100644 --- a/res/include/script/admin.js +++ b/res/include/script/admin.js @@ -1,32 +1,32 @@ let graphViews = drawGraph(document.getElementById("views_chart"), "Views", "number"); let graphBandwidth = drawGraph(document.getElementById("bandwidth_chart"), "Bandwidth", "bytes"); let graphTimeout = null; -function loadGraph(minutes, interval, live){ +function loadGraph(minutes, interval, live) { if (graphTimeout !== null) { clearTimeout(graphTimeout) } if (live) { - graphTimeout = setTimeout(() => {loadGraph(minutes, interval, true)}, 10000) + graphTimeout = setTimeout(() => { loadGraph(minutes, interval, true) }, 10000) } let today = new Date() let start = new Date() - start.setMinutes(start.getMinutes()-minutes) + start.setMinutes(start.getMinutes() - minutes) fetch( - apiEndpoint+"/admin/files/timeseries" + - "?start="+start.toISOString() + - "&end="+today.toISOString() + - "&interval="+interval + apiEndpoint + "/admin/files/timeseries" + + "?start=" + start.toISOString() + + "&end=" + today.toISOString() + + "&interval=" + interval ).then(resp => { - if (!resp.ok) { return Promise.reject("Error: "+resp.status);} + if (!resp.ok) { return Promise.reject("Error: " + resp.status); } return resp.json(); }).then(resp => { resp.views.timestamps.forEach((val, idx) => { let date = new Date(val); - let dateStr = ("00"+(date.getMonth()+1)).slice(-2); - dateStr += "-"+("00"+date.getDate()).slice(-2); - dateStr += " "+("00"+date.getHours()).slice(-2); - dateStr += ":"+("00"+date.getMinutes()).slice(-2); - resp.views.timestamps[idx] = " "+dateStr+" "; // Poor man's padding + let dateStr = ("00" + (date.getMonth() + 1)).slice(-2); + dateStr += "-" + ("00" + date.getDate()).slice(-2); + dateStr += " " + ("00" + date.getHours()).slice(-2); + dateStr += ":" + ("00" + date.getMinutes()).slice(-2); + resp.views.timestamps[idx] = " " + dateStr + " "; // Poor man's padding }); graphViews.data.labels = resp.views.timestamps; graphViews.data.datasets[0].data = resp.views.amounts; @@ -44,30 +44,30 @@ function loadGraph(minutes, interval, live){ resp.views.amounts.forEach(e => { total += e; }); document.getElementById("total_views").innerText = formatThousands(total); }).catch(e => { - alert("Error requesting time series: "+e); + alert("Error requesting time series: " + e); }) } loadGraph(10080, 60, false); -function loadGraphDate(start, end, interval){ +function loadGraphDate(start, end, interval) { fetch( - apiEndpoint+"/admin/files/timeseries" + - "?start="+start.toISOString() + - "&end="+end.toISOString() + - "&interval="+interval + apiEndpoint + "/admin/files/timeseries" + + "?start=" + start.toISOString() + + "&end=" + end.toISOString() + + "&interval=" + interval ).then(resp => { - if (!resp.ok) { return Promise.reject("Error: "+resp.status);} + if (!resp.ok) { return Promise.reject("Error: " + resp.status); } return resp.json(); }).then(resp => { resp.views.timestamps.forEach((val, idx) => { let date = new Date(val); let dateStr = date.getUTCFullYear() + - "-"+("00"+(date.getUTCMonth()+1)).slice(-2) + - "-"+("00"+date.getUTCDate()).slice(-2) + - " "+("00"+date.getUTCHours()).slice(-2) + - ":"+("00"+date.getUTCMinutes()).slice(-2); - resp.views.timestamps[idx] = " "+dateStr+" "; // Poor man's padding + "-" + ("00" + (date.getUTCMonth() + 1)).slice(-2) + + "-" + ("00" + date.getUTCDate()).slice(-2) + + " " + ("00" + date.getUTCHours()).slice(-2) + + ":" + ("00" + date.getUTCMinutes()).slice(-2); + resp.views.timestamps[idx] = " " + dateStr + " "; // Poor man's padding }); graphViews.data.labels = resp.views.timestamps; graphViews.data.datasets[0].data = resp.views.amounts; @@ -85,7 +85,7 @@ function loadGraphDate(start, end, interval){ resp.views.amounts.forEach(e => { total += e; }); document.getElementById("total_views").innerText = formatThousands(total); }).catch(e => { - alert("Error requesting time series: "+e); + alert("Error requesting time series: " + e); }) } @@ -114,19 +114,19 @@ function loadTimespan(span, base) { loadGraphDate(monday, sunday, 60); } else if (span === "month") { let start = new Date(Date.UTC(base.getUTCFullYear(), base.getUTCMonth(), 1)); - let end = new Date(Date.UTC(base.getUTCFullYear(), base.getUTCMonth()+1, 0, 23, 59, 59)); + let end = new Date(Date.UTC(base.getUTCFullYear(), base.getUTCMonth() + 1, 0, 23, 59, 59)); tsStart = start; loadGraphDate(start, end, 60); } else if (span === "quarter") { - let start = new Date(Date.UTC(base.getUTCFullYear(), 3.0 * Math.floor(base.getUTCMonth()/3.0), 1)); - let end = new Date(Date.UTC(base.getUTCFullYear(), 3.0 * Math.ceil(base.getUTCMonth()/3.0), 0, 23, 59, 59)); + let start = new Date(Date.UTC(base.getUTCFullYear(), 3.0 * Math.floor(base.getUTCMonth() / 3.0), 1)); + let end = new Date(Date.UTC(base.getUTCFullYear(), 3.0 * Math.ceil(base.getUTCMonth() / 3.0), 0, 23, 59, 59)); tsStart = start; loadGraphDate(start, end, 1440); } else if (span === "year") { let start = new Date(Date.UTC(base.getUTCFullYear(), 0, 1)); - let end = new Date(Date.UTC(base.getUTCFullYear()+1, 0, 0, 23, 59, 59)); + let end = new Date(Date.UTC(base.getUTCFullYear() + 1, 0, 0, 23, 59, 59)); tsStart = start; loadGraphDate(start, end, 1440); @@ -139,21 +139,21 @@ function loadTimespan(span, base) { function navigateTimespan(forward) { let offYear = 0, offMonth = 0, offDay = 0; switch (tsSpan) { - case "day": - offDay = 1; - break; - case "week": - offDay = 7; - break; - case "month": - offMonth = 1; - break; - case "quarter": - offMonth = 3; - break; - case "year": - offYear = 1; - break; + case "day": + offDay = 1; + break; + case "week": + offDay = 7; + break; + case "month": + offMonth = 1; + break; + case "quarter": + offMonth = 3; + break; + case "year": + offYear = 1; + break; } if (!forward) { @@ -165,9 +165,9 @@ function navigateTimespan(forward) { loadTimespan( tsSpan, new Date(Date.UTC( - tsStart.getUTCFullYear()+offYear, - tsStart.getUTCMonth()+offMonth, - tsStart.getUTCDay()+offDay, + tsStart.getUTCFullYear() + offYear, + tsStart.getUTCMonth() + offMonth, + tsStart.getUTCDay() + offDay, )) ) } @@ -184,7 +184,7 @@ let lastOrder; function getStats(order) { lastOrder = order - fetch(apiEndpoint+"/status").then( + fetch(apiEndpoint + "/status").then( resp => resp.json() ).then(resp => { document.getElementById("file_stats_watchers").innerText = resp.stats_watcher_threads; @@ -210,19 +210,8 @@ function getStats(order) { last_remote_read_size = resp.remote_read_size; - let c = document.getElementById("tconnstat_body") - c.innerHTML = "" - resp.db_connection_stats.forEach(v => { - let row = document.createElement("tr") - row.innerHTML = `\ - ${v.name} - ${v.available} - ${v.max_connections} - ${v.open_connections} - ${v.connections_in_use} - ${v.connections_idle}` - c.appendChild(row) - }) + document.getElementById("db_time").innerText = printDate(new Date(resp.db_time), true, true, true); + document.getElementById("db_latency").innerText = formatNumber(resp.db_latency / 1000, 3) + " ms"; let p = document.getElementById("tbody_peers") p.innerHTML = "" @@ -237,7 +226,7 @@ function getStats(order) { ${formatDataVolume(v.free_space, 3)} ${formatDataVolume(v.min_free_space, 3)}` - if (v.free_space < v.min_free_space/2 || !v.reachable) { + if (v.free_space < v.min_free_space / 2 || !v.reachable) { row.classList.add("highlight_red") } else if (v.free_space < v.min_free_space) { row.classList.add("highlight_blue") @@ -248,7 +237,7 @@ function getStats(order) { }) resp.query_statistics.sort((a, b) => { - if (typeof(a[order]) === "number") { + if (typeof (a[order]) === "number") { // Sort ints from high to low return b[order] - a[order] } else { @@ -279,4 +268,4 @@ function getStats(order) { } getStats("calls") -setInterval(() => { getStats(lastOrder) }, 5000) +setInterval(() => { getStats(lastOrder) }, 5000) diff --git a/res/include/style/layout.css b/res/include/style/layout.css index 58a9193..b155a86 100644 --- a/res/include/style/layout.css +++ b/res/include/style/layout.css @@ -24,6 +24,7 @@ font-family: 'Material Icons'; font-style: normal; font-weight: 400; + font-display: block; src: local('Material Icons'), local('MaterialIcons-Regular'), url(/res/misc/MaterialIcons-Regular.woff2) format('woff2'), @@ -95,7 +96,7 @@ body{ position: fixed; backface-visibility: hidden; z-index: 99; - width: 250px; + width: 18em; height: 100%; left: 0; float: left; @@ -115,7 +116,7 @@ body{ z-index: 200; right: 0; height: auto; - left: 250px; + left: 18em; min-width: 300px; display: inline-block; text-align: center; /* Center the header and body */ @@ -127,7 +128,7 @@ body{ } @media (max-width: 800px) { .page_navigation { - left: -250px; + left: -18em; } .page_body { left: 0; diff --git a/res/template/admin.html b/res/template/admin.html index a891853..f583c29 100644 --- a/res/template/admin.html +++ b/res/template/admin.html @@ -15,8 +15,7 @@

Bandwidth and views

- - + @@ -52,19 +51,13 @@
-

Database connection statistics

- - - - - - - - - - - + + + + + +
NameAvailableMaxOpenIn useIdle
DB TimeDB Latency

Pixelstore peers

diff --git a/res/template/fragments/page_wrap.html b/res/template/fragments/page_wrap.html index 5c90a2c..46c0fc3 100644 --- a/res/template/fragments/page_wrap.html +++ b/res/template/fragments/page_wrap.html @@ -15,9 +15,9 @@ {{end}}
About - - APIAppearance + API + Acknowledgements + + + on:paste={paste} />
- +
Drop files here to upload them
{#each upload_jobs as c}
-  {c.file.name} 
+  {c.file.name} 
-
+
{/each}
- - diff --git a/svelte/src/user_buckets/UserBuckets.svelte b/svelte/src/user_buckets/UserBuckets.svelte index 52bfcfa..4a6eaff 100644 --- a/svelte/src/user_buckets/UserBuckets.svelte +++ b/svelte/src/user_buckets/UserBuckets.svelte @@ -3,37 +3,81 @@ import { onMount } from "svelte"; import Spinner from "../util/Spinner.svelte"; import { fs_get_buckets } from "../filesystem/FilesystemAPI.svelte"; -let loading = true -let buckets = [] +let loading = true; +let buckets = []; const get_buckets = async () => { try { - let resp = await fs_get_buckets() - buckets = resp.buckets + let resp = await fs_get_buckets(); + buckets = resp.buckets; } catch (err) { - alert(err) + alert(err); } finally { - loading = false + loading = false; } +}; + +const expand_bucket = () => { + } -onMount(get_buckets) - +onMount(get_buckets);
{#if loading} -
+
+ +
{/if} - {#each buckets as bucket} - {bucket.name} - {/each} +
+ {#each buckets as bucket} + +
{bucket.name}
+ +
+
+ Hello! +
+ {/each} +
+ diff --git a/webcontroller/web_controller.go b/webcontroller/web_controller.go index ffa5b0d..7d91e4f 100644 --- a/webcontroller/web_controller.go +++ b/webcontroller/web_controller.go @@ -104,21 +104,22 @@ func New( handler httprouter.Handle // The function to run when this API is called }{ // General navigation - {GET, "" /* */, wc.serveTemplate("home", false)}, - {GET, "api" /* */, wc.serveMarkdown("apidoc.md", false)}, - {GET, "history" /* */, wc.serveTemplate("history_cookies", false)}, - {GET, "u/:id" /* */, wc.serveFileViewer}, - {GET, "u/:id/preview" /**/, wc.serveFilePreview}, - {GET, "l/:id" /* */, wc.serveListViewer}, - {GET, "s/:id" /* */, wc.serveSkynetViewer}, - {GET, "t" /* */, wc.serveTemplate("text_editor", false)}, - {GET, "donation" /* */, wc.serveMarkdown("donation.md", false)}, - {GET, "subscribe" /* */, wc.serveMarkdown("subscribe.md", false)}, - {GET, "widgets" /* */, wc.serveTemplate("widgets", false)}, - {GET, "about" /* */, wc.serveMarkdown("about.md", false)}, - {GET, "appearance" /* */, wc.serveTemplate("appearance", false)}, - {GET, "hosting" /* */, wc.serveMarkdown("hosting.md", false)}, - {GET, "brave" /* */, wc.serveMarkdown("brave.md", false)}, + {GET, "" /* */, wc.serveTemplate("home", false)}, + {GET, "api" /* */, wc.serveMarkdown("apidoc.md", false)}, + {GET, "history" /* */, wc.serveTemplate("history_cookies", false)}, + {GET, "u/:id" /* */, wc.serveFileViewer}, + {GET, "u/:id/preview" /* */, wc.serveFilePreview}, + {GET, "l/:id" /* */, wc.serveListViewer}, + {GET, "s/:id" /* */, wc.serveSkynetViewer}, + {GET, "t" /* */, wc.serveTemplate("text_editor", false)}, + {GET, "donation" /* */, wc.serveMarkdown("donation.md", false)}, + {GET, "subscribe" /* */, wc.serveMarkdown("subscribe.md", false)}, + {GET, "widgets" /* */, wc.serveTemplate("widgets", false)}, + {GET, "about" /* */, wc.serveMarkdown("about.md", false)}, + {GET, "appearance" /* */, wc.serveTemplate("appearance", false)}, + {GET, "hosting" /* */, wc.serveMarkdown("hosting.md", false)}, + {GET, "brave" /* */, wc.serveMarkdown("brave.md", false)}, + {GET, "acknowledgements" /**/, wc.serveMarkdown("acknowledgements.md", false)}, // User account pages {GET, "register" /* */, wc.serveForm(wc.registerForm, false)},