Custom pages: Implement adding custom markdown pages to the site navigation

This commit is contained in:
Andrei Marcu 2020-03-12 13:32:35 -07:00
parent 9f6346a373
commit fb0d44f132
14 changed files with 207 additions and 91 deletions

1
.gitignore vendored
View File

@ -34,4 +34,5 @@ linx-genkey
files/
meta/
binaries/
custom_pages/
authfile

View File

@ -56,6 +56,7 @@ allowhotlink = true
- ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...)
- ```-nologs``` -- (optionally) disable request logs in stdout
- ```-force-random-filename``` -- (optionally) force the use of random filenames
- ```-custompagespath "custom_pages"``` -- (optionally) specify path to directory containing markdown pages (must end in .md) that will be added to the site navigation (this can be useful for providing contact/support information and so on). For example, custom_pages/My_Page.md will become My Page in the site navigation
#### Require API Keys for uploads
- ```-authfile path/to/authfile``` -- (optionally) require authorization for upload/delete by providing a newline-separated file of scrypted auth keys

40
custom_pages.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
"io/ioutil"
"log"
"path"
"strings"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday"
)
func initializeCustomPages(customPagesDir string) {
files, err := ioutil.ReadDir(customPagesDir)
if err != nil {
log.Fatal("Error reading the custom pages directory: ", err)
}
for _, file := range files {
fileName := file.Name()
if len(fileName) <= 3 {
continue
}
if strings.EqualFold(string(fileName[len(fileName)-3:len(fileName)]), ".md") {
contents, err := ioutil.ReadFile(path.Join(customPagesDir, fileName))
if err != nil {
log.Fatalf("Error reading file %s", fileName)
}
unsafe := blackfriday.MarkdownCommon(contents)
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
fileName := fileName[0 : len(fileName)-3]
customPages[fileName] = string(html)
customPagesNames[fileName] = strings.ReplaceAll(fileName, "_", " ")
}
}
}

View File

@ -50,6 +50,21 @@ func apiDocHandler(c web.C, w http.ResponseWriter, r *http.Request) {
}
}
func makeCustomPageHandler(fileName string) func(c web.C, w http.ResponseWriter, r *http.Request) {
return func(c web.C, w http.ResponseWriter, r *http.Request) {
err := renderTemplate(Templates["custom_page.html"], pongo2.Context{
"siteurl": getSiteURL(r),
"forcerandom": Config.forceRandomFilename,
"contents": customPages[fileName],
"filename": fileName,
"pagename": customPagesNames[fileName],
}, r, w)
if err != nil {
oopsHandler(c, w, r, RespHTML, "")
}
}
}
func notFoundHandler(c web.C, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
err := renderTemplate(Templates["404.html"], pongo2.Context{}, r, w)

View File

@ -70,6 +70,7 @@ var Config struct {
s3ForcePathStyle bool
forceRandomFilename bool
accessKeyCookieExpiry uint64
customPagesDir string
}
var Templates = make(map[string]*pongo2.Template)
@ -80,6 +81,8 @@ var timeStartedStr string
var remoteAuthKeys []string
var metaStorageBackend backends.MetaStorageBackend
var storageBackend backends.StorageBackend
var customPages = make(map[string]string)
var customPagesNames = make(map[string]string)
func setup() *web.Mux {
mux := web.New()
@ -227,6 +230,14 @@ func setup() *web.Mux {
mux.Get(selifIndexRe, unauthorizedHandler)
mux.Get(torrentRe, fileTorrentHandler)
if Config.customPagesDir != "" {
initializeCustomPages(Config.customPagesDir)
for fileName := range customPagesNames {
mux.Get(Config.sitePath+fileName, makeCustomPageHandler(fileName))
mux.Get(Config.sitePath+fileName+"/", makeCustomPageHandler(fileName))
}
}
mux.NotFound(notFoundHandler)
return mux
@ -298,6 +309,8 @@ func main() {
flag.BoolVar(&Config.forceRandomFilename, "force-random-filename", false,
"Force all uploads to use a random filename")
flag.Uint64Var(&Config.accessKeyCookieExpiry, "access-cookie-expiry", 0, "Expiration time for access key cookies in seconds (set 0 to use session cookies)")
flag.StringVar(&Config.customPagesDir, "custompagespath", "",
"path to directory containing .md files to render as custom pages")
iniflags.Parse()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -52,6 +52,7 @@ func populateTemplatesMap(tSet *pongo2.TemplateSet, tMap map[string]*pongo2.Temp
"404.html",
"oops.html",
"access.html",
"custom_page.html",
"display/audio.html",
"display/image.html",
@ -85,6 +86,8 @@ func renderTemplate(tpl *pongo2.Template, context pongo2.Context, r *http.Reques
context["sitepath"] = Config.sitePath
context["selifpath"] = Config.selifPath
context["custom_pages_names"] = customPagesNames
var a string
if Config.authFile == "" {
a = "none"

View File

@ -1,7 +1,9 @@
{% extends "base.html" %}
{% block title %}{{sitename}} - 404 Not Found{% endblock %}
{% block content %}
<div class="error-404">
<a href="{{ sitepath }}"><img src='{{ sitepath }}static/images/404.jpg'></a>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,59 +1,64 @@
{% extends "base.html" %}
{% block title %}{{sitename}} - API{% endblock %}
{% block head %}
<link href="{{ sitepath }}static/css/github-markdown.css" rel="stylesheet" type="text/css">
{% endblock %}
{% block content %}
<div id="main">
<div id='inner_content'>
<div id='inner_content'>
<div class="normal markdown-body">
<h2>API</h2>
<h3>Client</h3>
<p>To simplify uploading and deleting files, you can use <a target="_blank" href="https://github.com/andreimarcu/linx-client">linx-client</a>, which uses this API.</p>
<p>To simplify uploading and deleting files, you can use <a target="_blank"
href="https://github.com/andreimarcu/linx-client">linx-client</a>, which uses this API.</p>
{% if auth != "none" %}
<h3>Keys</h3>
<p>This instance uses API Keys, therefore you will need to provide a key for uploading and deleting files.<br/> To do so, add the <code>Linx-Api-Key</code> header with your key.</p>
<p>This instance uses API Keys, therefore you will need to provide a key for uploading and deleting
files.<br /> To do so, add the <code>Linx-Api-Key</code> header with your key.</p>
{% endif %}
<h3>Uploading a file</h3>
<p>To upload a file, make a PUT request to <code>{{ siteurl }}upload/</code> and you will get the url of your upload back.</p>
<p>To upload a file, make a PUT request to <code>{{ siteurl }}upload/</code> and you will get the url of
your upload back.</p>
<p><strong>Optional headers with the request</strong></p>
{% if not forcerandom %}
<p>Randomize the filename<br/>
<code>Linx-Randomize: yes</code></p>
{% endif %}
{% if not forcerandom %}
<p>Randomize the filename<br />
<code>Linx-Randomize: yes</code></p>
{% endif %}
<p>Specify a custom deletion key<br/>
<code>Linx-Delete-Key: mysecret</code></p>
<p>Specify a custom deletion key<br />
<code>Linx-Delete-Key: mysecret</code></p>
<p>Protect file with password<br/>
<code>Linx-Access-Key: mysecret</code></p>
<p>Protect file with password<br />
<code>Linx-Access-Key: mysecret</code></p>
<p>Specify an expiration time (in seconds)<br/>
<code>Linx-Expiry: 60</code></p>
<p>Specify an expiration time (in seconds)<br />
<code>Linx-Expiry: 60</code></p>
<p>Get a json response<br/>
<code>Accept: application/json</code></p>
<p>Get a json response<br />
<code>Accept: application/json</code></p>
<p>The json response will then contain:</p>
<blockquote>
<p>“url”: the publicly available upload url<br/>
“direct_url”: the url to access the file directly<br/>
“filename”: the (optionally generated) filename<br/>
“delete_key”: the (optionally generated) deletion key,<br/>
“access_key”: the (optionally supplied) access key,<br/>
“expiry”: the unix timestamp at which the file will expire (0 if never)<br/>
“size”: the size in bytes of the file<br/>
“mimetype”: the guessed mimetype of the file<br/>
“sha256sum”: the sha256sum of the file,</p>
<p>“url”: the publicly available upload url<br />
“direct_url”: the url to access the file directly<br />
“filename”: the (optionally generated) filename<br />
“delete_key”: the (optionally generated) deletion key,<br />
“access_key”: the (optionally supplied) access key,<br />
“expiry”: the unix timestamp at which the file will expire (0 if never)<br />
“size”: the size in bytes of the file<br />
“mimetype”: the guessed mimetype of the file<br />
“sha256sum”: the sha256sum of the file,</p>
</blockquote>
<p><strong>Examples</strong></p>
@ -92,7 +97,8 @@
<h3>Overwriting a file</h3>
<p>To overwrite a file you uploaded, simply provide the <code>Linx-Delete-Key</code> header with the original file's deletion key.</p>
<p>To overwrite a file you uploaded, simply provide the <code>Linx-Delete-Key</code> header with the
original file's deletion key.</p>
<p><strong>Example</p></strong>
@ -108,7 +114,8 @@
<h3>Deleting a file</h3>
<p>To delete a file you uploaded, make a DELETE request to <code>{{ siteurl }}yourfile.ext</code> with the delete key set as the <code>Linx-Delete-Key</code> header.</p>
<p>To delete a file you uploaded, make a DELETE request to <code>{{ siteurl }}yourfile.ext</code> with the
delete key set as the <code>Linx-Delete-Key</code> header.</p>
<p><strong>Example</strong></p>
@ -124,16 +131,17 @@ DELETED</code></pre>
<h3>Information about a file</h3>
<p>To retrieve information about a file, make a GET request the public url with <code>Accept: application/json</code> headers and you will receive a json response containing:</p>
<p>To retrieve information about a file, make a GET request the public url with
<code>Accept: application/json</code> headers and you will receive a json response containing:</p>
<blockquote>
<p>“url”: the publicly available upload url<br/>
“direct_url”: the url to access the file directly<br/>
“filename”: the (optionally generated) filename<br/>
“expiry”: the unix timestamp at which the file will expire (0 if never)<br/>
“size”: the size in bytes of the file<br/>
“mimetype”: the guessed mimetype of the file<br/>
“sha256sum”: the sha256sum of the file,</p>
<p>“url”: the publicly available upload url<br />
“direct_url”: the url to access the file directly<br />
“filename”: the (optionally generated) filename<br />
“expiry”: the unix timestamp at which the file will expire (0 if never)<br />
“size”: the size in bytes of the file<br />
“mimetype”: the guessed mimetype of the file<br />
“sha256sum”: the sha256sum of the file,</p>
</blockquote>
<p><strong>Example</strong></p>
@ -141,6 +149,6 @@ DELETED</code></pre>
<pre><code>$ curl -H &#34;Accept: application/json&#34; {{ siteurl }}myphoto.jpg
{&#34;expiry&#34;:&#34;0&#34;,&#34;filename&#34;:&#34;myphoto.jpg&#34;,&#34;mimetype&#34;:&#34;image/jpeg&#34;,&#34;sha256sum&#34;:&#34;...&#34;,&#34;size&#34;:&#34;...&#34;}</code></pre>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,5 +1,7 @@
{% extends "base.html" %}
{% block title %}{{sitename}} - Password protected file{% endblock %}
{% block content %}
<div id="main" class="oopscontent">
<form action="{{ unlockpath }}" method="POST" enctype="multipart/form-data">
@ -9,4 +11,4 @@
<br /><br />
</form>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{{ sitename }}{% endblock %}</title>
<meta charset='utf-8' content='text/html' http-equiv='content-type'>
@ -16,12 +17,15 @@
<div id="header">
<div id="navigation" class="right">
{% if auth != "header" %}
<a href="{{ sitepath }}">Upload</a> |
<a href="{{ sitepath }}paste/">Paste</a> |
<a href="{{ sitepath }}">Upload</a> |
<a href="{{ sitepath }}paste/">Paste</a> |
{% endif %}
<a href="{{ sitepath }}API/">API</a>
{% for custom_file_name, custom_page_name in custom_pages_names sorted %}
| <a href="{{ sitepath }}{{ custom_file_name }}/">{{ custom_page_name }}</a>
{% endfor %}
</div>
<h2><a href="{{ sitepath }}" title="{{ sitename }}">{{ sitename }}</a></h2>
<h2><a href="{{ sitepath }}" title="{{ sitename }}">{{ sitename }}</a></h2>
</div>
{% block content %}{% endblock %}
@ -33,4 +37,5 @@
</div>
</div>
</body>
</html>
</html>

View File

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block title %}{{sitename}} - {{ pagename }}{% endblock %}
{% block head %}
<link href="{{ sitepath }}static/css/github-markdown.css" rel="stylesheet" type="text/css">
{% endblock %}
{% block content %}
<div id="main">
<div id='inner_content'>
<div class="normal markdown-body">
<h2>{{ pagename }}</h2>
{{ contents|safe }}
</div>
</div>
</div>
{% endblock %}

View File

@ -1,36 +1,36 @@
{% extends "../base.html" %}
{% block title %}{{ filename }}{% endblock %}
{% block title %}{{sitename}} - {{ filename }}{% endblock %}
{% block bodymore %}{% endblock %}
{% block content %}
<div id="info" class="dinfo info-flex">
<div id="filename">
{{ filename }}
</div>
<div class="info-actions">
{% if expiry %}
<span>file expires in {{ expiry }}</span> |
{% endif %}
{% block infomore %}{% endblock %}
<span>{{ size }}</span> |
<a href="{{ filename }}/torrent" download>torrent</a> |
<a href="{{ sitepath }}{{ selifpath }}{{ filename }}" download>get</a>
</div>
{% block infoleft %}{% endblock %}
<div id="info" class="dinfo info-flex">
<div id="filename">
{{ filename }}
</div>
<div id="main" {% block mainmore %}{% endblock %}>
<div id='inner_content' {% block innercontentmore %}{% endblock %} >
{% block main %}{% endblock %}
</div>
<div class="info-actions">
{% if expiry %}
<span>file expires in {{ expiry }}</span> |
{% endif %}
{% block infomore %}{% endblock %}
<span>{{ size }}</span> |
<a href="{{ filename }}/torrent" download>torrent</a> |
<a href="{{ sitepath }}{{ selifpath }}{{ filename }}" download>get</a>
</div>
<script src="{{ sitepath }}static/js/clipboard.js"></script>
{% endblock %}
{% block infoleft %}{% endblock %}
</div>
<div id="main" {% block mainmore %}{% endblock %}>
<div id='inner_content' {% block innercontentmore %}{% endblock %}>
{% block main %}{% endblock %}
</div>
</div>
<script src="{{ sitepath }}static/js/clipboard.js"></script>
{% endblock %}

View File

@ -1,33 +1,40 @@
{% extends "base.html" %}
{% block title %}{{sitename}} - Paste{% endblock %}
{% block content %}
<form id="reply" action='{{ sitepath }}upload' method='post'>
<div id="main" class="paste">
<div id="info" class="info-flex">
<div>
{% if not forcerandom %}<span class="hint--top hint--bounce" data-hint="Leave empty for random filename"><input class="codebox" name='filename' id="filename" type='text' value="" placeholder="filename" /></span>{% endif %}.<span class="hint--top hint--bounce" data-hint="Enable syntax highlighting by adding the extension"><input id="extension" class="codebox" name='extension' type='text' value="" placeholder="txt" /></span>
</div>
<div>
<span class="hint--top hint--bounce" data-hint="Require password to access (leave empty to disable)">
<input class="codebox" name="access_key" type="text" placeholder="password"/>
</span>
<select id="expiry" name="expires">
<option disabled>Expires:</option>
{% for expiry in expirylist %}
<option value="{{ expiry.Seconds }}"{% if forloop.Last %} selected{% endif %}>{{ expiry.Human }}</option>
{% endfor %}
</select>
<button type="submit">Paste</button>
</div>
<form id="reply" action='{{ sitepath }}upload' method='post'>
<div id="main" class="paste">
<div id="info" class="info-flex">
<div>
{% if not forcerandom %}<span class="hint--top hint--bounce"
data-hint="Leave empty for random filename"><input class="codebox" name='filename' id="filename"
type='text' value="" placeholder="filename" /></span>{% endif %}.<span
class="hint--top hint--bounce" data-hint="Enable syntax highlighting by adding the extension"><input
id="extension" class="codebox" name='extension' type='text' value="" placeholder="txt" /></span>
</div>
<div>
<span class="hint--top hint--bounce" data-hint="Require password to access (leave empty to disable)">
<input class="codebox" name="access_key" type="text" placeholder="password" />
</span>
<div id="inner_content" class="padme">
<textarea name='content' id="content" class="editor"></textarea>
<select id="expiry" name="expires">
<option disabled>Expires:</option>
{% for expiry in expirylist %}
<option value="{{ expiry.Seconds }}" {% if forloop.Last %} selected{% endif %}>{{ expiry.Human }}
</option>
{% endfor %}
</select>
<button type="submit">Paste</button>
</div>
</div>
</form>
<div id="inner_content" class="padme">
<textarea name='content' id="content" class="editor"></textarea>
</div>
</div>
</form>
<script src="{{ sitepath }}static/js/util.js"></script>
<script src="{{ sitepath }}static/js/paste.js"></script>
{% endblock %}
{% endblock %}