From 88c00027ea23f75f54ba1e41f28659e49d73a14f Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Thu, 12 Nov 2015 11:17:53 -0500 Subject: [PATCH 001/233] Add line numbers for pastebin. Fixes #70 --- static/css/highlight/tomorrow.css | 13 +++++++++++++ static/js/bin_hljs.js | 1 + static/js/highlight/highlightjs-line-numbers.min.js | 3 +++ templates/display/bin.html | 1 + 4 files changed, 18 insertions(+) create mode 100644 static/js/highlight/highlightjs-line-numbers.min.js diff --git a/static/css/highlight/tomorrow.css b/static/css/highlight/tomorrow.css index ae9d1f8..9bc0645 100644 --- a/static/css/highlight/tomorrow.css +++ b/static/css/highlight/tomorrow.css @@ -79,6 +79,19 @@ padding: 0.5em; } +.hljs-line-numbers { + text-align: right; + border-right: 1px solid #ccc; + margin-right: 5px; + color: #999; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + .coffeescript .javascript, .javascript .xml, .tex .hljs-formula, diff --git a/static/js/bin_hljs.js b/static/js/bin_hljs.js index 61e7f52..5ccbd53 100644 --- a/static/js/bin_hljs.js +++ b/static/js/bin_hljs.js @@ -2,5 +2,6 @@ hljs.tabReplace = ' '; hljs.initHighlightingOnLoad(); +hljs.initLineNumbersOnLoad(); // @license-end diff --git a/static/js/highlight/highlightjs-line-numbers.min.js b/static/js/highlight/highlightjs-line-numbers.min.js new file mode 100644 index 0000000..8ef50bc --- /dev/null +++ b/static/js/highlight/highlightjs-line-numbers.min.js @@ -0,0 +1,3 @@ +// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat +!function(e){"use strict";function t(){e.addEventListener("load",function(){try{var e=document.querySelectorAll("code.hljs");for(var t in e)e.hasOwnProperty(t)&&n(e[t])}catch(r){console.error("LineNumbers error: ",r)}})}function n(e){if("object"==typeof e){var t=e.parentNode,n=r(t.textContent);if(n>1){for(var o="",l=0;n>l;l++)o+=l+1+"\n";var c=document.createElement("code");c.className="hljs hljs-line-numbers",c.style["float"]="left",c.textContent=o,t.insertBefore(c,e)}}}function r(e){if(0===e.length)return 0;var t=/\r\n|\r|\n/g,n=e.match(t);return n=n?n.length:0,e[e.length-1].match(t)||(n+=1),n}"undefined"==typeof e.hljs?console.error("highlight.js not detected!"):(e.hljs.initLineNumbersOnLoad=t,e.hljs.lineNumbersBlock=n)}(window); +// @license-end \ No newline at end of file diff --git a/templates/display/bin.html b/templates/display/bin.html index bfa66c2..00d8177 100644 --- a/templates/display/bin.html +++ b/templates/display/bin.html @@ -44,6 +44,7 @@ {% if extra.lang_hl != "text" %} + {% endif %} From 7c1a4640dbf1ae06867eb8c7ef152e58daae6de6 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Wed, 2 Dec 2015 14:58:48 -0500 Subject: [PATCH 002/233] Order matters! --- display.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/display.go b/display.go index 6678550..4591522 100644 --- a/display.go +++ b/display.go @@ -77,6 +77,15 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } else if metadata.Mimetype == "application/pdf" { tpl = Templates["display/pdf.html"] + } else if extension == "story" { + if metadata.Size < maxDisplayFileSizeBytes { + bytes, err := ioutil.ReadFile(filePath) + if err == nil { + extra["contents"] = string(bytes) + lines = strings.Split(extra["contents"], "\n") + tpl = Templates["display/story.html"] + } + } } else if strings.HasPrefix(metadata.Mimetype, "text/") || supportedBinExtension(extension) { if metadata.Size < maxDisplayFileSizeBytes { bytes, err := ioutil.ReadFile(filePath) @@ -87,15 +96,6 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { tpl = Templates["display/bin.html"] } } - } else if extension == "story" { - if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := ioutil.ReadFile(filePath) - if err == nil { - extra["contents"] = string(bytes) - lines = strings.Split(extra["contents"], "\n") - tpl = Templates["display/story.html"] - } - } } else if extension == "md" { if metadata.Size < maxDisplayFileSizeBytes { bytes, err := ioutil.ReadFile(filePath) From 5dcfca5f7490194ea128e4df3adb60549a2d10ee Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Fri, 4 Dec 2015 00:19:33 -0500 Subject: [PATCH 003/233] Order matters no. 2 --- display.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/display.go b/display.go index 4591522..45727c8 100644 --- a/display.go +++ b/display.go @@ -86,16 +86,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { tpl = Templates["display/story.html"] } } - } else if strings.HasPrefix(metadata.Mimetype, "text/") || supportedBinExtension(extension) { - if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := ioutil.ReadFile(filePath) - if err == nil { - extra["extension"] = extension - extra["lang_hl"], extra["lang_ace"] = extensionToHlAndAceLangs(extension) - extra["contents"] = string(bytes) - tpl = Templates["display/bin.html"] - } - } + } else if extension == "md" { if metadata.Size < maxDisplayFileSizeBytes { bytes, err := ioutil.ReadFile(filePath) @@ -107,6 +98,17 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { tpl = Templates["display/md.html"] } } + + } else if strings.HasPrefix(metadata.Mimetype, "text/") || supportedBinExtension(extension) { + if metadata.Size < maxDisplayFileSizeBytes { + bytes, err := ioutil.ReadFile(filePath) + if err == nil { + extra["extension"] = extension + extra["lang_hl"], extra["lang_ace"] = extensionToHlAndAceLangs(extension) + extra["contents"] = string(bytes) + tpl = Templates["display/bin.html"] + } + } } // Catch other files From 817ac67632f9392a5bb9971679ff239914ebc764 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Fri, 4 Dec 2015 19:28:30 -0800 Subject: [PATCH 004/233] workaround chrome nonsense with CSP Apparently the Chromium developers have decided that it was a good idea for them to use inline styles on the image/PDF viewers in their browser. I have no idea why they would think this, as it is not, but since this causes breakage we allow unsafe-inline for styles on files. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 9a1bc8c..535da6d 100644 --- a/server.go +++ b/server.go @@ -201,7 +201,7 @@ func main() { "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; referrer origin;", "value of default Content-Security-Policy header") flag.StringVar(&Config.fileContentSecurityPolicy, "filecontentsecuritypolicy", - "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; referrer origin;", + "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; referrer origin;", "value of Content-Security-Policy header for file access") flag.StringVar(&Config.xFrameOptions, "xframeoptions", "SAMEORIGIN", "value of X-Frame-Options header") From b0d2f2a1423dba3e8f651e3c28c5315b48e7f46c Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Fri, 12 Feb 2016 21:27:39 -0800 Subject: [PATCH 005/233] support .tar.gz-style extensions Some extensions actually consist of multiple parts, like .tar.gz, so we should handle this properly instead of merging part of the extension with the bare name. Right now only tar is allowed, but others can be added easily. Fixes #74. --- server_test.go | 18 ++++++++++++++++++ upload.go | 25 ++++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/server_test.go b/server_test.go index e653dec..374f229 100644 --- a/server_test.go +++ b/server_test.go @@ -887,6 +887,24 @@ func TestPutAndSpecificDelete(t *testing.T) { } } +func TestExtension(t *testing.T) { + barename, extension := barePlusExt("test.jpg.gz") + if barename != "testjpg" { + t.Fatal("Barename was not testjpg, but " + barename) + } + if extension != "gz" { + t.Fatal("Extension was not gz, but " + extension) + } + + barename, extension = barePlusExt("test.tar.gz") + if barename != "test" { + t.Fatal("Barename was not test, but " + barename) + } + if extension != "tar.gz" { + t.Fatal("Extension was not tar.gz, but " + extension) + } +} + func TestShutdown(t *testing.T) { os.RemoveAll(Config.filesDir) os.RemoveAll(Config.metaDir) diff --git a/upload.go b/upload.go index 33efd87..2fd4ec3 100644 --- a/upload.go +++ b/upload.go @@ -322,20 +322,35 @@ func generateJSONresponse(upload Upload) []byte { return js } -var barePlusRe = regexp.MustCompile(`[^A-Za-z0-9\-]`) +var bareRe = regexp.MustCompile(`[^A-Za-z0-9\-]`) +var extRe = regexp.MustCompile(`[^A-Za-z0-9\-\.]`) +var compressedExts = map[string]bool{ + ".bz2": true, + ".gz": true, + ".xz": true, +} +var archiveExts = map[string]bool{ + ".tar": true, +} func barePlusExt(filename string) (barename, extension string) { - filename = strings.TrimSpace(filename) filename = strings.ToLower(filename) extension = path.Ext(filename) barename = filename[:len(filename)-len(extension)] + if compressedExts[extension] { + ext2 := path.Ext(barename) + if archiveExts[ext2] { + barename = barename[:len(barename)-len(ext2)] + extension = ext2 + extension + } + } - extension = barePlusRe.ReplaceAllString(extension, "") - barename = barePlusRe.ReplaceAllString(barename, "") + extension = extRe.ReplaceAllString(extension, "") + barename = bareRe.ReplaceAllString(barename, "") - extension = strings.Trim(extension, "-") + extension = strings.Trim(extension, "-.") barename = strings.Trim(barename, "-") return From 39bb999db65fdac15d41c321cc66467a9b6c93c4 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Fri, 3 Jun 2016 22:49:01 -0700 Subject: [PATCH 006/233] Add ability to set arbitrary headers This is useful if you want to add headers for things like HTTP Strict Transport Security or HTTP Public Key Pinning. --- headers.go | 27 +++++++++++++++++++++++++++ server.go | 16 ++++++++++++++++ server_test.go | 18 ++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 headers.go diff --git a/headers.go b/headers.go new file mode 100644 index 0000000..918a6dd --- /dev/null +++ b/headers.go @@ -0,0 +1,27 @@ +package main + +import ( + "net/http" + "strings" +) + +type addheaders struct { + h http.Handler + headers []string +} + +func (a addheaders) ServeHTTP(w http.ResponseWriter, r *http.Request) { + for _, header := range a.headers { + headerSplit := strings.SplitN(header, ": ", 2) + w.Header().Add(headerSplit[0], headerSplit[1]) + } + + a.h.ServeHTTP(w, r) +} + +func AddHeaders(headers []string) func(http.Handler) http.Handler { + fn := func(h http.Handler) http.Handler { + return addheaders{h, headers} + } + return fn +} diff --git a/server.go b/server.go index 535da6d..8c129b6 100644 --- a/server.go +++ b/server.go @@ -10,6 +10,7 @@ import ( "os" "regexp" "strconv" + "strings" "time" "github.com/GeertJohan/go.rice" @@ -20,6 +21,17 @@ import ( "github.com/zenazn/goji/web/middleware" ) +type headerList []string + +func (h *headerList) String() string { + return strings.Join(*h, ",") +} + +func (h *headerList) Set(value string) error { + *h = append(*h, value) + return nil +} + var Config struct { bind string filesDir string @@ -40,6 +52,7 @@ var Config struct { remoteUploads bool authFile string remoteAuthFile string + addHeaders headerList } var Templates = make(map[string]*pongo2.Template) @@ -69,6 +82,7 @@ func setup() *web.Mux { policy: Config.contentSecurityPolicy, frame: Config.xFrameOptions, })) + mux.Use(AddHeaders(Config.addHeaders)) if Config.authFile != "" { mux.Use(UploadAuth(AuthOptions{ @@ -205,6 +219,8 @@ func main() { "value of Content-Security-Policy header for file access") flag.StringVar(&Config.xFrameOptions, "xframeoptions", "SAMEORIGIN", "value of X-Frame-Options header") + flag.Var(&Config.addHeaders, "addheader", + "Add an arbitrary header to the response. This option can be used multiple times.") iniflags.Parse() diff --git a/server_test.go b/server_test.go index 374f229..5cb1d52 100644 --- a/server_test.go +++ b/server_test.go @@ -52,6 +52,24 @@ func TestIndex(t *testing.T) { } } +func TestAddHeader(t *testing.T) { + Config.addHeaders = []string{"Linx-Test: It works!"} + + mux := setup() + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if w.Header().Get("Linx-Test") != "It works!" { + t.Fatal("Header 'Linx-Test: It works!' not found in index response") + } +} + func TestAuthKeys(t *testing.T) { Config.authFile = "/dev/null" From afa65349cc256f1b3bc5e51844aaebdff9a021e1 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Sat, 4 Jun 2016 00:20:20 -0700 Subject: [PATCH 007/233] Fix typos in API template --- templates/API.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/API.html b/templates/API.html index f46af5e..aab73b1 100644 --- a/templates/API.html +++ b/templates/API.html @@ -74,13 +74,13 @@

Uploading myphoto.jpg with a random filename and getting a json response:

{% if using_auth %} -
$ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}/upload/  
+			
$ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/  
 {"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
-"sha256sum":"...","size":"...","url":"{{ steurl }}/f34h4iu.jpg"}
+"sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"}
{% else %}
$ curl -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}/upload/  
 {"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
-"sha256sum":"...","size":"...","url":"{{ steurl }}/f34h4iu.jpg"}
+"sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"} {% endif %}

Overwriting a file

From 2b5cc07005ffda41c537ab463ac280a62f23f4c2 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sat, 4 Jun 2016 15:04:32 -0700 Subject: [PATCH 008/233] Use Content-Security-Policy frame-ancestors X-Frame-Options has been deprecated by Content Security Policy Level 2. We will need to provide the option for older browsers for the time being, but frame-ancestors is preferred on newer ones. --- README.md | 4 ++-- server.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98f5718..18c63aa 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ remoteuploads = true - ```-metapath meta/``` -- Path to store information about uploads (default is meta/) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) - ```-allowhotlink``` -- Allow file hotlinking -- ```-contentsecuritypolicy "..."``` -- Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; referrer origin;") -- ```-filecontentsecuritypolicy "..."``` -- Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; referrer origin;") +- ```-contentsecuritypolicy "..."``` -- Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") +- ```-filecontentsecuritypolicy "..."``` -- Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") - ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN") - ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...) - ```-nologs``` -- (optionally) disable request logs in stdout diff --git a/server.go b/server.go index 8c129b6..28a36a9 100644 --- a/server.go +++ b/server.go @@ -212,10 +212,10 @@ func main() { flag.StringVar(&Config.remoteAuthFile, "remoteauthfile", "", "path to a file containing newline-separated scrypted auth keys for remote uploads") flag.StringVar(&Config.contentSecurityPolicy, "contentsecuritypolicy", - "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; referrer origin;", + "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;", "value of default Content-Security-Policy header") flag.StringVar(&Config.fileContentSecurityPolicy, "filecontentsecuritypolicy", - "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; referrer origin;", + "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;", "value of Content-Security-Policy header for file access") flag.StringVar(&Config.xFrameOptions, "xframeoptions", "SAMEORIGIN", "value of X-Frame-Options header") From 47670af185f8f4ee0c2e63053acfb2994445d8f6 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sat, 4 Jun 2016 01:22:01 -0700 Subject: [PATCH 009/233] Infer site URL from host and headers We can use the Host property of the request and the X-Forwarded-Proto to infer the site URL. To reduce complexity, the path is not inferred, and it is assumed that linx-server is running at /. If this is not the case, the site URL must be manually configured; this is no different than it was before. --- fileserve.go | 2 +- headers.go | 24 ++++++++++++ pages.go | 2 +- server.go | 31 +++++++++------- server_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ torrent.go | 6 +-- torrent_test.go | 4 +- upload.go | 14 +++---- 8 files changed, 152 insertions(+), 28 deletions(-) diff --git a/fileserve.go b/fileserve.go index 00858f0..c7eac21 100644 --- a/fileserve.go +++ b/fileserve.go @@ -26,7 +26,7 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { if !Config.allowHotlink { referer := r.Header.Get("Referer") u, _ := url.Parse(referer) - p, _ := url.Parse(Config.siteURL) + p, _ := url.Parse(getSiteURL(r)) if referer != "" && !sameOrigin(u, p) { http.Redirect(w, r, Config.sitePath+fileName, 303) return diff --git a/headers.go b/headers.go index 918a6dd..d0eeda0 100644 --- a/headers.go +++ b/headers.go @@ -2,6 +2,7 @@ package main import ( "net/http" + "net/url" "strings" ) @@ -25,3 +26,26 @@ func AddHeaders(headers []string) func(http.Handler) http.Handler { } return fn } + +func getSiteURL(r *http.Request) string { + if Config.siteURL != "" { + return Config.siteURL + } else { + u := &url.URL{} + u.Host = r.Host + + if Config.sitePath != "" { + u.Path = Config.sitePath + } + + if scheme := r.Header.Get("X-Forwarded-Proto"); scheme != "" { + u.Scheme = scheme + } else if Config.certFile != "" || (r.TLS != nil && r.TLS.HandshakeComplete == true) { + u.Scheme = "https" + } else { + u.Scheme = "http" + } + + return u.String() + } +} diff --git a/pages.go b/pages.go index 9d24bba..597e5a4 100644 --- a/pages.go +++ b/pages.go @@ -36,7 +36,7 @@ func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) { } func apiDocHandler(c web.C, w http.ResponseWriter, r *http.Request) { - err := Templates["API.html"].ExecuteWriter(pongo2.Context{}, w) + err := Templates["API.html"].ExecuteWriter(pongo2.Context{"siteurl": getSiteURL(r)}, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") } diff --git a/server.go b/server.go index 8c129b6..e7eee43 100644 --- a/server.go +++ b/server.go @@ -102,17 +102,21 @@ func setup() *web.Mux { log.Fatal("Could not create metadata directory:", err) } - // ensure siteURL ends wth '/' - if lastChar := Config.siteURL[len(Config.siteURL)-1:]; lastChar != "/" { - Config.siteURL = Config.siteURL + "/" - } + if Config.siteURL != "" { + // ensure siteURL ends wth '/' + if lastChar := Config.siteURL[len(Config.siteURL)-1:]; lastChar != "/" { + Config.siteURL = Config.siteURL + "/" + } - parsedUrl, err := url.Parse(Config.siteURL) - if err != nil { - log.Fatal("Could not parse siteurl:", err) - } + parsedUrl, err := url.Parse(Config.siteURL) + if err != nil { + log.Fatal("Could not parse siteurl:", err) + } - Config.sitePath = parsedUrl.Path + Config.sitePath = parsedUrl.Path + } else { + Config.sitePath = "/" + } // Template setup p2l, err := NewPongo2TemplatesLoader() @@ -121,7 +125,6 @@ func setup() *web.Mux { } TemplateSet := pongo2.NewSet("templates", p2l) TemplateSet.Globals["sitename"] = Config.siteName - TemplateSet.Globals["siteurl"] = Config.siteURL TemplateSet.Globals["sitepath"] = Config.sitePath TemplateSet.Globals["using_auth"] = Config.authFile != "" err = populateTemplatesMap(TemplateSet, Templates) @@ -193,7 +196,7 @@ func main() { "Allow hotlinking of files") flag.StringVar(&Config.siteName, "sitename", "linx", "name of the site") - flag.StringVar(&Config.siteURL, "siteurl", "http://"+Config.bind+"/", + flag.StringVar(&Config.siteURL, "siteurl", "", "site base url (including trailing slash)") flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024, "maximum upload file size in bytes (default 4GB)") @@ -232,16 +235,16 @@ func main() { log.Fatal("Could not bind: ", err) } - log.Printf("Serving over fastcgi, bound on %s, using siteurl %s", Config.bind, Config.siteURL) + log.Printf("Serving over fastcgi, bound on %s", Config.bind) fcgi.Serve(listener, mux) } else if Config.certFile != "" { - log.Printf("Serving over https, bound on %s, using siteurl %s", Config.bind, Config.siteURL) + log.Printf("Serving over https, bound on %s", Config.bind) err := graceful.ListenAndServeTLS(Config.bind, Config.certFile, Config.keyFile, mux) if err != nil { log.Fatal(err) } } else { - log.Printf("Serving over http, bound on %s, using siteurl %s", Config.bind, Config.siteURL) + log.Printf("Serving over http, bound on %s", Config.bind) err := graceful.ListenAndServe(Config.bind, mux) if err != nil { log.Fatal(err) diff --git a/server_test.go b/server_test.go index 5cb1d52..527e3be 100644 --- a/server_test.go +++ b/server_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "crypto/tls" "encoding/json" "mime/multipart" "net/http" @@ -923,6 +924,102 @@ func TestExtension(t *testing.T) { } } +func TestInferSiteURL(t *testing.T) { + oldSiteURL := Config.siteURL + oldSitePath := Config.sitePath + Config.siteURL = "" + Config.sitePath = "/linxtest/" + + mux := setup() + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/API/", nil) + req.Host = "example.com:8080" + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if !strings.Contains(w.Body.String(), "http://example.com:8080/upload/") { + t.Fatal("Site URL not found properly embedded in response") + } + + Config.siteURL = oldSiteURL + Config.sitePath = oldSitePath +} + +func TestInferSiteURLProxied(t *testing.T) { + oldSiteURL := Config.siteURL + Config.siteURL = "" + + mux := setup() + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/API/", nil) + req.Header.Add("X-Forwarded-Proto", "https") + req.Host = "example.com:8080" + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if !strings.Contains(w.Body.String(), "https://example.com:8080/upload/") { + t.Fatal("Site URL not found properly embedded in response") + } + + Config.siteURL = oldSiteURL +} + +func TestInferSiteURLHTTPS(t *testing.T) { + oldSiteURL := Config.siteURL + oldCertFile := Config.certFile + Config.siteURL = "" + Config.certFile = "/dev/null" + + mux := setup() + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/API/", nil) + req.Host = "example.com" + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if !strings.Contains(w.Body.String(), "https://example.com/upload/") { + t.Fatal("Site URL not found properly embedded in response") + } + + Config.siteURL = oldSiteURL + Config.certFile = oldCertFile +} + +func TestInferSiteURLHTTPSFastCGI(t *testing.T) { + oldSiteURL := Config.siteURL + Config.siteURL = "" + + mux := setup() + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/API/", nil) + req.Host = "example.com" + req.TLS = &tls.ConnectionState{HandshakeComplete: true} + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if !strings.Contains(w.Body.String(), "https://example.com/upload/") { + t.Fatal("Site URL not found properly embedded in response") + } + + Config.siteURL = oldSiteURL +} + func TestShutdown(t *testing.T) { os.RemoveAll(Config.filesDir) os.RemoveAll(Config.metaDir) diff --git a/torrent.go b/torrent.go index e3c4952..2e82598 100644 --- a/torrent.go +++ b/torrent.go @@ -37,7 +37,7 @@ func hashPiece(piece []byte) []byte { return h.Sum(nil) } -func createTorrent(fileName string, filePath string) ([]byte, error) { +func createTorrent(fileName string, filePath string, r *http.Request) ([]byte, error) { chunk := make([]byte, TORRENT_PIECE_LENGTH) torrent := Torrent{ @@ -46,7 +46,7 @@ func createTorrent(fileName string, filePath string) ([]byte, error) { PieceLength: TORRENT_PIECE_LENGTH, Name: fileName, }, - UrlList: []string{fmt.Sprintf("%sselif/%s", Config.siteURL, fileName)}, + UrlList: []string{fmt.Sprintf("%sselif/%s", getSiteURL(r), fileName)}, } f, err := os.Open(filePath) @@ -89,7 +89,7 @@ func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } - encoded, err := createTorrent(fileName, filePath) + encoded, err := createTorrent(fileName, filePath, r) if err != nil { oopsHandler(c, w, r, RespHTML, "Could not create torrent.") return diff --git a/torrent_test.go b/torrent_test.go index 9a44eb9..03a51da 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -11,7 +11,7 @@ func TestCreateTorrent(t *testing.T) { fileName := "server.go" var decoded Torrent - encoded, err := createTorrent(fileName, fileName) + encoded, err := createTorrent(fileName, fileName, nil) if err != nil { t.Fatal(err) } @@ -47,7 +47,7 @@ func TestCreateTorrent(t *testing.T) { func TestCreateTorrentWithImage(t *testing.T) { var decoded Torrent - encoded, err := createTorrent("test.jpg", "static/images/404.jpg") + encoded, err := createTorrent("test.jpg", "static/images/404.jpg", nil) if err != nil { t.Fatal(err) } diff --git a/upload.go b/upload.go index 2fd4ec3..33d845d 100644 --- a/upload.go +++ b/upload.go @@ -46,7 +46,7 @@ type Upload struct { } func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { - if !strictReferrerCheck(r, Config.siteURL, []string{"Linx-Delete-Key", "Linx-Expiry", "Linx-Randomize", "X-Requested-With"}) { + if !strictReferrerCheck(r, getSiteURL(r), []string{"Linx-Delete-Key", "Linx-Expiry", "Linx-Randomize", "X-Requested-With"}) { badRequestHandler(c, w, r) return } @@ -94,7 +94,7 @@ func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } - js := generateJSONresponse(upload) + js := generateJSONresponse(upload, r) w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Write(js) } else { @@ -124,7 +124,7 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } - js := generateJSONresponse(upload) + js := generateJSONresponse(upload, r) w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Write(js) } else { @@ -133,7 +133,7 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } - fmt.Fprintf(w, Config.siteURL+upload.Filename) + fmt.Fprintf(w, getSiteURL(r)+upload.Filename) } } @@ -174,7 +174,7 @@ func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) { return } - js := generateJSONresponse(upload) + js := generateJSONresponse(upload, r) w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Write(js) } else { @@ -308,9 +308,9 @@ func generateBarename() string { return uniuri.NewLenChars(8, []byte("abcdefghijklmnopqrstuvwxyz0123456789")) } -func generateJSONresponse(upload Upload) []byte { +func generateJSONresponse(upload Upload, r *http.Request) []byte { js, _ := json.Marshal(map[string]string{ - "url": Config.siteURL + upload.Filename, + "url": getSiteURL(r) + upload.Filename, "filename": upload.Filename, "delete_key": upload.Metadata.DeleteKey, "expiry": strconv.FormatInt(upload.Metadata.Expiry.Unix(), 10), From fcd18eceec5c637d9a102dc63698e270fee35103 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Mon, 6 Jun 2016 23:37:42 -0700 Subject: [PATCH 010/233] use abstracted storage for flexibility I moved the storage functionality into the StorageBackend interface, which is currently only implemented by LocalfsBackend. --- backends/backends.go | 23 ++++++++++++ backends/localfs/localfs.go | 70 +++++++++++++++++++++++++++++++++++++ delete.go | 9 ++--- display.go | 12 +++---- fileserve.go | 13 +++---- meta.go | 27 ++++++-------- server.go | 7 ++++ torrent.go | 21 +++++------ torrent_test.go | 17 +++++++-- upload.go | 32 +++++------------ 10 files changed, 154 insertions(+), 77 deletions(-) create mode 100644 backends/backends.go create mode 100644 backends/localfs/localfs.go diff --git a/backends/backends.go b/backends/backends.go new file mode 100644 index 0000000..42a33f0 --- /dev/null +++ b/backends/backends.go @@ -0,0 +1,23 @@ +package backends + +import ( + "io" + "net/http" +) + +type ReadSeekCloser interface { + io.Reader + io.Closer + io.Seeker + io.ReaderAt +} + +type StorageBackend interface { + Delete(key string) error + Exists(key string) (bool, error) + Get(key string) ([]byte, error) + Put(key string, r io.Reader) (int64, error) + Open(key string) (ReadSeekCloser, error) + ServeFile(key string, w http.ResponseWriter, r *http.Request) + Size(key string) (int64, error) +} diff --git a/backends/localfs/localfs.go b/backends/localfs/localfs.go new file mode 100644 index 0000000..148cf2e --- /dev/null +++ b/backends/localfs/localfs.go @@ -0,0 +1,70 @@ +package localfs + +import ( + "errors" + "io" + "io/ioutil" + "net/http" + "os" + "path" + + "github.com/andreimarcu/linx-server/backends" +) + +type LocalfsBackend struct { + basePath string +} + +func (b LocalfsBackend) Delete(key string) error { + return os.Remove(path.Join(b.basePath, key)) +} + +func (b LocalfsBackend) Exists(key string) (bool, error) { + _, err := os.Stat(path.Join(b.basePath, key)) + return err == nil, err +} + +func (b LocalfsBackend) Get(key string) ([]byte, error) { + return ioutil.ReadFile(path.Join(b.basePath, key)) +} + +func (b LocalfsBackend) Put(key string, r io.Reader) (int64, error) { + dst, err := os.Create(path.Join(b.basePath, key)) + if err != nil { + return 0, err + } + defer dst.Close() + + bytes, err := io.Copy(dst, r) + if bytes == 0 { + b.Delete(key) + return bytes, errors.New("Empty file") + } else if err != nil { + b.Delete(key) + return bytes, err + } + + return bytes, err +} + +func (b LocalfsBackend) Open(key string) (backends.ReadSeekCloser, error) { + return os.Open(path.Join(b.basePath, key)) +} + +func (b LocalfsBackend) ServeFile(key string, w http.ResponseWriter, r *http.Request) { + filePath := path.Join(b.basePath, key) + http.ServeFile(w, r, filePath) +} + +func (b LocalfsBackend) Size(key string) (int64, error) { + fileInfo, err := os.Stat(path.Join(b.basePath, key)) + if err != nil { + return 0, err + } + + return fileInfo.Size(), nil +} + +func NewLocalfsBackend(basePath string) LocalfsBackend { + return LocalfsBackend{basePath: basePath} +} diff --git a/delete.go b/delete.go index f727f60..e42e623 100644 --- a/delete.go +++ b/delete.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "os" - "path" "github.com/zenazn/goji/web" ) @@ -13,11 +12,9 @@ func deleteHandler(c web.C, w http.ResponseWriter, r *http.Request) { requestKey := r.Header.Get("Linx-Delete-Key") filename := c.URLParams["name"] - filePath := path.Join(Config.filesDir, filename) - metaPath := path.Join(Config.metaDir, filename) // Ensure requested file actually exists - if _, readErr := os.Stat(filePath); os.IsNotExist(readErr) { + if _, readErr := fileBackend.Exists(filename); os.IsNotExist(readErr) { notFoundHandler(c, w, r) // 404 - file doesn't exist return } @@ -30,8 +27,8 @@ func deleteHandler(c web.C, w http.ResponseWriter, r *http.Request) { } if metadata.DeleteKey == requestKey { - fileDelErr := os.Remove(filePath) - metaDelErr := os.Remove(metaPath) + fileDelErr := fileBackend.Delete(filename) + metaDelErr := metaBackend.Delete(filename) if (fileDelErr != nil) || (metaDelErr != nil) { oopsHandler(c, w, r, RespPLAIN, "Could not delete") diff --git a/display.go b/display.go index 45727c8..9105119 100644 --- a/display.go +++ b/display.go @@ -2,10 +2,7 @@ package main import ( "encoding/json" - "io/ioutil" "net/http" - "os" - "path" "path/filepath" "strconv" "strings" @@ -22,7 +19,6 @@ const maxDisplayFileSizeBytes = 1024 * 512 func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - filePath := path.Join(Config.filesDir, fileName) err := checkFile(fileName) if err == NotFoundErr { @@ -43,7 +39,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { extra := make(map[string]string) lines := []string{} - file, _ := os.Open(filePath) + file, _ := fileBackend.Open(fileName) defer file.Close() header := make([]byte, 512) @@ -79,7 +75,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } else if extension == "story" { if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := ioutil.ReadFile(filePath) + bytes, err := fileBackend.Get(fileName) if err == nil { extra["contents"] = string(bytes) lines = strings.Split(extra["contents"], "\n") @@ -89,7 +85,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } else if extension == "md" { if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := ioutil.ReadFile(filePath) + bytes, err := fileBackend.Get(fileName) if err == nil { unsafe := blackfriday.MarkdownCommon(bytes) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) @@ -101,7 +97,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } else if strings.HasPrefix(metadata.Mimetype, "text/") || supportedBinExtension(extension) { if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := ioutil.ReadFile(filePath) + bytes, err := fileBackend.Get(fileName) if err == nil { extra["extension"] = extension extra["lang_hl"], extra["lang_ace"] = extensionToHlAndAceLangs(extension) diff --git a/fileserve.go b/fileserve.go index c7eac21..1a48a74 100644 --- a/fileserve.go +++ b/fileserve.go @@ -3,8 +3,6 @@ package main import ( "net/http" "net/url" - "os" - "path" "strings" "github.com/zenazn/goji/web" @@ -12,7 +10,6 @@ import ( func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - filePath := path.Join(Config.filesDir, fileName) err := checkFile(fileName) if err == NotFoundErr { @@ -35,7 +32,7 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Security-Policy", Config.fileContentSecurityPolicy) - http.ServeFile(w, r, filePath) + fileBackend.ServeFile(fileName, w, r) } func staticHandler(c web.C, w http.ResponseWriter, r *http.Request) { @@ -63,9 +60,7 @@ func staticHandler(c web.C, w http.ResponseWriter, r *http.Request) { } func checkFile(filename string) error { - filePath := path.Join(Config.filesDir, filename) - - _, err := os.Stat(filePath) + _, err := fileBackend.Exists(filename) if err != nil { return NotFoundErr } @@ -76,8 +71,8 @@ func checkFile(filename string) error { } if expired { - os.Remove(path.Join(Config.filesDir, filename)) - os.Remove(path.Join(Config.metaDir, filename)) + fileBackend.Delete(filename) + metaBackend.Delete(filename) return NotFoundErr } diff --git a/meta.go b/meta.go index 0c6928d..7487176 100644 --- a/meta.go +++ b/meta.go @@ -3,6 +3,7 @@ package main import ( "archive/tar" "archive/zip" + "bytes" "compress/bzip2" "compress/gzip" "crypto/sha256" @@ -10,9 +11,6 @@ import ( "encoding/json" "errors" "io" - "io/ioutil" - "os" - "path" "sort" "time" "unicode" @@ -43,14 +41,17 @@ var NotFoundErr = errors.New("File not found.") var BadMetadata = errors.New("Corrupted metadata.") func generateMetadata(fName string, exp time.Time, delKey string) (m Metadata, err error) { - file, err := os.Open(path.Join(Config.filesDir, fName)) - fileInfo, err := os.Stat(path.Join(Config.filesDir, fName)) + file, err := fileBackend.Open(fName) if err != nil { return } defer file.Close() - m.Size = fileInfo.Size() + m.Size, err = fileBackend.Size(fName) + if err != nil { + return + } + m.Expiry = exp if delKey == "" { @@ -138,12 +139,6 @@ func generateMetadata(fName string, exp time.Time, delKey string) (m Metadata, e } func metadataWrite(filename string, metadata *Metadata) error { - file, err := os.Create(path.Join(Config.metaDir, filename)) - if err != nil { - return err - } - defer file.Close() - mjson := MetadataJSON{} mjson.DeleteKey = metadata.DeleteKey mjson.Mimetype = metadata.Mimetype @@ -157,8 +152,7 @@ func metadataWrite(filename string, metadata *Metadata) error { return err } - _, err = file.Write(byt) - if err != nil { + if _, err := metaBackend.Put(filename, bytes.NewBuffer(byt)); err != nil { return err } @@ -166,7 +160,7 @@ func metadataWrite(filename string, metadata *Metadata) error { } func metadataRead(filename string) (metadata Metadata, err error) { - b, err := ioutil.ReadFile(path.Join(Config.metaDir, filename)) + b, err := metaBackend.Get(filename) if err != nil { // Metadata does not exist, generate one newMData, err := generateMetadata(filename, neverExpire, "") @@ -174,7 +168,8 @@ func metadataRead(filename string) (metadata Metadata, err error) { return metadata, err } metadataWrite(filename, &newMData) - b, err = ioutil.ReadFile(path.Join(Config.metaDir, filename)) + + b, err = metaBackend.Get(filename) if err != nil { return metadata, BadMetadata } diff --git a/server.go b/server.go index e7eee43..6e0b279 100644 --- a/server.go +++ b/server.go @@ -14,6 +14,8 @@ import ( "time" "github.com/GeertJohan/go.rice" + "github.com/andreimarcu/linx-server/backends" + "github.com/andreimarcu/linx-server/backends/localfs" "github.com/flosch/pongo2" "github.com/vharitonsky/iniflags" "github.com/zenazn/goji/graceful" @@ -61,6 +63,8 @@ var staticBox *rice.Box var timeStarted time.Time var timeStartedStr string var remoteAuthKeys []string +var metaBackend backends.StorageBackend +var fileBackend backends.StorageBackend func setup() *web.Mux { mux := web.New() @@ -118,6 +122,9 @@ func setup() *web.Mux { Config.sitePath = "/" } + metaBackend = localfs.NewLocalfsBackend(Config.metaDir) + fileBackend = localfs.NewLocalfsBackend(Config.filesDir) + // Template setup p2l, err := NewPongo2TemplatesLoader() if err != nil { diff --git a/torrent.go b/torrent.go index 2e82598..f0f4d48 100644 --- a/torrent.go +++ b/torrent.go @@ -6,8 +6,6 @@ import ( "fmt" "io" "net/http" - "os" - "path" "time" "github.com/zeebo/bencode" @@ -37,7 +35,7 @@ func hashPiece(piece []byte) []byte { return h.Sum(nil) } -func createTorrent(fileName string, filePath string, r *http.Request) ([]byte, error) { +func createTorrent(fileName string, f io.ReadCloser, r *http.Request) ([]byte, error) { chunk := make([]byte, TORRENT_PIECE_LENGTH) torrent := Torrent{ @@ -49,11 +47,6 @@ func createTorrent(fileName string, filePath string, r *http.Request) ([]byte, e UrlList: []string{fmt.Sprintf("%sselif/%s", getSiteURL(r), fileName)}, } - f, err := os.Open(filePath) - if err != nil { - return []byte{}, err - } - for { n, err := f.Read(chunk) if err == io.EOF { @@ -66,8 +59,6 @@ func createTorrent(fileName string, filePath string, r *http.Request) ([]byte, e torrent.Info.Pieces += string(hashPiece(chunk[:n])) } - f.Close() - data, err := bencode.EncodeBytes(&torrent) if err != nil { return []byte{}, err @@ -78,7 +69,6 @@ func createTorrent(fileName string, filePath string, r *http.Request) ([]byte, e func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - filePath := path.Join(Config.filesDir, fileName) err := checkFile(fileName) if err == NotFoundErr { @@ -89,7 +79,14 @@ func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } - encoded, err := createTorrent(fileName, filePath, r) + f, err := fileBackend.Open(fileName) + if err != nil { + oopsHandler(c, w, r, RespHTML, "Could not create torrent.") + return + } + defer f.Close() + + encoded, err := createTorrent(fileName, f, r) if err != nil { oopsHandler(c, w, r, RespHTML, "Could not create torrent.") return diff --git a/torrent_test.go b/torrent_test.go index 03a51da..38132f2 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "testing" "github.com/zeebo/bencode" @@ -11,7 +12,13 @@ func TestCreateTorrent(t *testing.T) { fileName := "server.go" var decoded Torrent - encoded, err := createTorrent(fileName, fileName, nil) + f, err := os.Open("server.go") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + encoded, err := createTorrent(fileName, f, nil) if err != nil { t.Fatal(err) } @@ -47,7 +54,13 @@ func TestCreateTorrent(t *testing.T) { func TestCreateTorrentWithImage(t *testing.T) { var decoded Torrent - encoded, err := createTorrent("test.jpg", "static/images/404.jpg", nil) + f, err := os.Open("static/images/404.jpg") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + encoded, err := createTorrent("test.jpg", f, nil) if err != nil { t.Fatal(err) } diff --git a/upload.go b/upload.go index 33d845d..b8b395b 100644 --- a/upload.go +++ b/upload.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/url" - "os" "path" "path/filepath" "regexp" @@ -232,9 +231,8 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { upload.Filename = strings.Join([]string{barename, extension}, ".") - _, err = os.Stat(path.Join(Config.filesDir, upload.Filename)) + fileexists, _ := fileBackend.Exists(upload.Filename) - fileexists := err == nil // Check if the delete key matches, in which case overwrite if fileexists { metad, merr := metadataRead(upload.Filename) @@ -254,20 +252,13 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { } upload.Filename = strings.Join([]string{barename, extension}, ".") - _, err = os.Stat(path.Join(Config.filesDir, upload.Filename)) - fileexists = err == nil + fileexists, err = fileBackend.Exists(upload.Filename) } if fileBlacklist[strings.ToLower(upload.Filename)] { return upload, errors.New("Prohibited filename") } - dst, err := os.Create(path.Join(Config.filesDir, upload.Filename)) - if err != nil { - return - } - defer dst.Close() - // Get the rest of the metadata needed for storage var expiry time.Time if upReq.expiry == 0 { @@ -276,29 +267,22 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { expiry = time.Now().Add(upReq.expiry) } - bytes, err := io.Copy(dst, io.MultiReader(bytes.NewReader(header), upReq.src)) - if bytes == 0 { - os.Remove(path.Join(Config.filesDir, upload.Filename)) - return upload, errors.New("Empty file") - - } else if err != nil { - os.Remove(path.Join(Config.filesDir, upload.Filename)) - return + bytes, err := fileBackend.Put(upload.Filename, io.MultiReader(bytes.NewReader(header), upReq.src)) + if err != nil { + return upload, err } else if bytes > Config.maxSize { - os.Remove(path.Join(Config.filesDir, upload.Filename)) + fileBackend.Delete(upload.Filename) return upload, errors.New("File too large") } upload.Metadata, err = generateMetadata(upload.Filename, expiry, upReq.deletionKey) if err != nil { - os.Remove(path.Join(Config.filesDir, upload.Filename)) - os.Remove(path.Join(Config.metaDir, upload.Filename)) + fileBackend.Delete(upload.Filename) return } err = metadataWrite(upload.Filename, &upload.Metadata) if err != nil { - os.Remove(path.Join(Config.filesDir, upload.Filename)) - os.Remove(path.Join(Config.metaDir, upload.Filename)) + fileBackend.Delete(upload.Filename) return } return From 47a1aa6396565b7f5db5bd04a5ee5d36b4d399d4 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Tue, 14 Jun 2016 23:21:39 -0700 Subject: [PATCH 011/233] Infer sitename from Host if empty --- display.go | 4 ++-- pages.go | 18 ++++++++++-------- server.go | 5 +---- templates.go | 14 ++++++++++++++ templates/API.html | 4 ++-- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/display.go b/display.go index 9105119..4d3896b 100644 --- a/display.go +++ b/display.go @@ -112,7 +112,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { tpl = Templates["display/file.html"] } - err = tpl.ExecuteWriter(pongo2.Context{ + err = renderTemplate(tpl, pongo2.Context{ "mime": metadata.Mimetype, "filename": fileName, "size": sizeHuman, @@ -120,7 +120,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { "extra": extra, "lines": lines, "files": metadata.ArchiveFiles, - }, w) + }, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") diff --git a/pages.go b/pages.go index 597e5a4..abd2cbd 100644 --- a/pages.go +++ b/pages.go @@ -20,23 +20,25 @@ const ( ) func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { - err := Templates["index.html"].ExecuteWriter(pongo2.Context{ + err := renderTemplate(Templates["index.html"], pongo2.Context{ "maxsize": Config.maxSize, - }, w) + }, r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) { - err := Templates["paste.html"].ExecuteWriter(pongo2.Context{}, w) + err := renderTemplate(Templates["paste.html"], pongo2.Context{}, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") } } func apiDocHandler(c web.C, w http.ResponseWriter, r *http.Request) { - err := Templates["API.html"].ExecuteWriter(pongo2.Context{"siteurl": getSiteURL(r)}, w) + err := renderTemplate(Templates["API.html"], pongo2.Context{ + "siteurl": getSiteURL(r), + }, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") } @@ -44,7 +46,7 @@ func apiDocHandler(c web.C, w http.ResponseWriter, r *http.Request) { func notFoundHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) - err := Templates["404.html"].ExecuteWriter(pongo2.Context{}, w) + err := renderTemplate(Templates["404.html"], pongo2.Context{}, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") } @@ -57,7 +59,7 @@ func oopsHandler(c web.C, w http.ResponseWriter, r *http.Request, rt RespType, m if rt == RespHTML { w.WriteHeader(500) - Templates["oops.html"].ExecuteWriter(pongo2.Context{"msg": msg}, w) + renderTemplate(Templates["oops.html"], pongo2.Context{"msg": msg}, r, w) return } else if rt == RespPLAIN { @@ -86,7 +88,7 @@ func oopsHandler(c web.C, w http.ResponseWriter, r *http.Request, rt RespType, m func badRequestHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) - err := Templates["400.html"].ExecuteWriter(pongo2.Context{}, w) + err := renderTemplate(Templates["400.html"], pongo2.Context{}, r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } @@ -94,7 +96,7 @@ func badRequestHandler(c web.C, w http.ResponseWriter, r *http.Request) { func unauthorizedHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) - err := Templates["401.html"].ExecuteWriter(pongo2.Context{}, w) + err := renderTemplate(Templates["401.html"], pongo2.Context{}, r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/server.go b/server.go index 2d2e3d4..d43f941 100644 --- a/server.go +++ b/server.go @@ -131,9 +131,6 @@ func setup() *web.Mux { log.Fatal("Error: could not load templates", err) } TemplateSet := pongo2.NewSet("templates", p2l) - TemplateSet.Globals["sitename"] = Config.siteName - TemplateSet.Globals["sitepath"] = Config.sitePath - TemplateSet.Globals["using_auth"] = Config.authFile != "" err = populateTemplatesMap(TemplateSet, Templates) if err != nil { log.Fatal("Error: could not load templates", err) @@ -201,7 +198,7 @@ func main() { "remove stdout output for each request") flag.BoolVar(&Config.allowHotlink, "allowhotlink", false, "Allow hotlinking of files") - flag.StringVar(&Config.siteName, "sitename", "linx", + flag.StringVar(&Config.siteName, "sitename", "", "name of the site") flag.StringVar(&Config.siteURL, "siteurl", "", "site base url (including trailing slash)") diff --git a/templates.go b/templates.go index aa4652a..04b8436 100644 --- a/templates.go +++ b/templates.go @@ -3,8 +3,10 @@ package main import ( "bytes" "io" + "net/http" "path" "path/filepath" + "strings" "github.com/GeertJohan/go.rice" "github.com/flosch/pongo2" @@ -71,3 +73,15 @@ func populateTemplatesMap(tSet *pongo2.TemplateSet, tMap map[string]*pongo2.Temp return nil } + +func renderTemplate(tpl *pongo2.Template, context pongo2.Context, r *http.Request, writer io.Writer) error { + if Config.siteName == "" { + parts := strings.Split(r.Host, ":") + context["sitename"] = parts[0] + } + + context["sitepath"] = Config.sitePath + context["using_auth"] = Config.authFile != "" + + return tpl.ExecuteWriter(context, writer) +} diff --git a/templates/API.html b/templates/API.html index aab73b1..045f4c2 100644 --- a/templates/API.html +++ b/templates/API.html @@ -78,7 +78,7 @@ {"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg", "sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"} {% else %} -
$ curl -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}/upload/  
+			
$ curl -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/  
 {"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
 "sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"}
{% endif %} @@ -130,7 +130,7 @@ DELETED

Example

-
$ curl -H "Accept: application/json" {{ siteurl }}/myphoto.jpg
+			
$ curl -H "Accept: application/json" {{ siteurl }}myphoto.jpg
 {"expiry":"0","filename":"myphoto.jpg","mimetype":"image/jpeg","sha256sum":"...","size":"..."}
From e8d2d75c1cfe3bd0d5ade22afe0fd71907e9493f Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Tue, 14 Jun 2016 23:38:36 -0700 Subject: [PATCH 012/233] Update documentation --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 18c63aa..c3dc252 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Self-hosted file/media sharing website. Get release and run ------------------- 1. Grab the latest binary from the [releases](https://github.com/andreimarcu/linx-server/releases) -2. Run ```./linx-server...``` +2. Run ```./linx-server``` Usage @@ -32,17 +32,16 @@ Usage #### Configuration All configuration options are accepted either as arguments or can be placed in an ini-style file as such: ```ini -sitename = "my linx" -siteurl = "https://linx.example.com" -remoteuploads = true +maxsize = 4294967296 +allowhotlink = true # etc ``` ...and then invoke ```linx-server -config path/to/config.ini``` -#### Common options +#### Options - ```-bind 127.0.0.1:8080``` -- what to bind to (default is 127.0.0.1:8080) -- ```-sitename myLinx``` -- the site name displayed on top (default is linx) -- ```-siteurl "http://mylinx.example.org/"``` -- the site url (for generating links) +- ```-sitename myLinx``` -- the site name displayed on top (default is inferred from Host header) +- ```-siteurl "http://mylinx.example.org/"``` -- the site url (default is inferred from execution context) - ```-filespath files/"``` -- Path to store uploads (default is files/) - ```-metapath meta/``` -- Path to store information about uploads (default is meta/) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) From d6755486b21451f051d8e11768167a3ebe49d6d0 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Wed, 15 Jun 2016 00:16:57 -0700 Subject: [PATCH 013/233] Sanitize upload response (Fixes #79) --- static/js/upload.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/js/upload.js b/static/js/upload.js index 1a79be1..6ebcfa6 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -88,6 +88,9 @@ Dropzone.options.dropzone = { if (resp.error) { file.fileLabel.innerHTML = file.name + ": " + resp.error; } + else if (resp.includes(" Date: Wed, 15 Jun 2016 01:17:25 -0700 Subject: [PATCH 014/233] Get rid of highlightjs-line-numbers.js --- static/css/highlight/lines.css | 28 +++++++++++++++++++ static/js/bin_hljs.js | 10 ++++++- .../highlight/highlightjs-line-numbers.min.js | 3 -- templates/display/bin.html | 2 +- 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 static/css/highlight/lines.css delete mode 100644 static/js/highlight/highlightjs-line-numbers.min.js diff --git a/static/css/highlight/lines.css b/static/css/highlight/lines.css new file mode 100644 index 0000000..b476e2a --- /dev/null +++ b/static/css/highlight/lines.css @@ -0,0 +1,28 @@ +#normal-code { + border-left: 1px solid #ccc; + margin-left: 36px; + position: relative; +} + +#normal-code code { + counter-reset: linenumbers; +} + +#normal-code code div:before { + content: counter(linenumbers); + counter-increment: linenumbers; + left: -36px; + width: 28px; + position: absolute; + + text-align: right; + padding-right: 5px; + color: #999; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} \ No newline at end of file diff --git a/static/js/bin_hljs.js b/static/js/bin_hljs.js index 5ccbd53..083ef4c 100644 --- a/static/js/bin_hljs.js +++ b/static/js/bin_hljs.js @@ -2,6 +2,14 @@ hljs.tabReplace = ' '; hljs.initHighlightingOnLoad(); -hljs.initLineNumbersOnLoad(); + +var codeb = document.getElementById("codeb"); +var lines = codeb.innerHTML.split("\n"); +codeb.innerHTML = ""; +for (var i = 0; i < lines.length; i++) { + var div = document.createElement("div"); + div.innerHTML = lines[i] + "\n"; + codeb.appendChild(div); +}; // @license-end diff --git a/static/js/highlight/highlightjs-line-numbers.min.js b/static/js/highlight/highlightjs-line-numbers.min.js deleted file mode 100644 index 8ef50bc..0000000 --- a/static/js/highlight/highlightjs-line-numbers.min.js +++ /dev/null @@ -1,3 +0,0 @@ -// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat -!function(e){"use strict";function t(){e.addEventListener("load",function(){try{var e=document.querySelectorAll("code.hljs");for(var t in e)e.hasOwnProperty(t)&&n(e[t])}catch(r){console.error("LineNumbers error: ",r)}})}function n(e){if("object"==typeof e){var t=e.parentNode,n=r(t.textContent);if(n>1){for(var o="",l=0;n>l;l++)o+=l+1+"\n";var c=document.createElement("code");c.className="hljs hljs-line-numbers",c.style["float"]="left",c.textContent=o,t.insertBefore(c,e)}}}function r(e){if(0===e.length)return 0;var t=/\r\n|\r|\n/g,n=e.match(t);return n=n?n.length:0,e[e.length-1].match(t)||(n+=1),n}"undefined"==typeof e.hljs?console.error("highlight.js not detected!"):(e.hljs.initLineNumbersOnLoad=t,e.hljs.lineNumbersBlock=n)}(window); -// @license-end \ No newline at end of file diff --git a/templates/display/bin.html b/templates/display/bin.html index 00d8177..a3aab36 100644 --- a/templates/display/bin.html +++ b/templates/display/bin.html @@ -2,6 +2,7 @@ {% block head %} + {% endblock %} {% block innercontentmore %} class="scrollable"{% endblock %} @@ -44,7 +45,6 @@ {% if extra.lang_hl != "text" %} - {% endif %} From f06b06fe76599f23a187c16f7c7fd77f01f2179a Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Wed, 15 Jun 2016 01:35:03 -0700 Subject: [PATCH 015/233] Add overflow-wrap to lines.css --- static/css/highlight/lines.css | 1 + 1 file changed, 1 insertion(+) diff --git a/static/css/highlight/lines.css b/static/css/highlight/lines.css index b476e2a..68c7a56 100644 --- a/static/css/highlight/lines.css +++ b/static/css/highlight/lines.css @@ -18,6 +18,7 @@ text-align: right; padding-right: 5px; color: #999; + overflow-wrap: normal; -webkit-touch-callout: none; -webkit-user-select: none; From aedd7bc5899467ff87675f3528ce62aff35f281b Mon Sep 17 00:00:00 2001 From: Atrox Date: Wed, 15 Jun 2016 14:33:51 +0200 Subject: [PATCH 016/233] Fix that custom sitename gets ignored --- templates.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates.go b/templates.go index 04b8436..0687bce 100644 --- a/templates.go +++ b/templates.go @@ -78,6 +78,8 @@ func renderTemplate(tpl *pongo2.Template, context pongo2.Context, r *http.Reques if Config.siteName == "" { parts := strings.Split(r.Host, ":") context["sitename"] = parts[0] + } else { + context["sitename"] = Config.siteName } context["sitepath"] = Config.sitePath From 027f5cce42b39c3fa0b7dc9766c5bc8aaa9a3cf6 Mon Sep 17 00:00:00 2001 From: Atrox Date: Wed, 15 Jun 2016 17:42:57 +0200 Subject: [PATCH 017/233] Add ability to short urls for uploaded content (#85) Add ability to short urls for uploaded content --- .travis.yml | 1 + README.md | 1 + display.go | 16 +- meta.go | 4 + server.go | 9 + shorturl.go | 89 +++++ static/css/hint.css | 450 ++++++++++++++++++++-- static/js/clipboard.js | 740 ++++++++++++++++++++++++++++++++++++ static/js/shorturl.js | 39 ++ templates/base.html | 1 + templates/display/base.html | 17 +- templates/paste.html | 4 - 12 files changed, 1317 insertions(+), 54 deletions(-) create mode 100644 shorturl.go create mode 100644 static/js/clipboard.js create mode 100644 static/js/shorturl.js diff --git a/.travis.yml b/.travis.yml index b82869c..eaf499f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: go go: - 1.5 + - 1.6 before_script: - go vet ./... diff --git a/README.md b/README.md index c3dc252..af844e3 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ allowhotlink = true - ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN") - ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...) - ```-nologs``` -- (optionally) disable request logs in stdout +- ```-googleapikey``` -- (optionally) API Key for Google's URL Shortener. ([How to create one](https://developers.google.com/url-shortener/v1/getting_started#APIKey)) #### SSL with built-in server - ```-certfile path/to/your.crt``` -- Path to the ssl certificate (required if you want to use the https server) diff --git a/display.go b/display.go index 4d3896b..f26b665 100644 --- a/display.go +++ b/display.go @@ -113,13 +113,15 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } err = renderTemplate(tpl, pongo2.Context{ - "mime": metadata.Mimetype, - "filename": fileName, - "size": sizeHuman, - "expiry": expiryHuman, - "extra": extra, - "lines": lines, - "files": metadata.ArchiveFiles, + "mime": metadata.Mimetype, + "filename": fileName, + "size": sizeHuman, + "expiry": expiryHuman, + "extra": extra, + "lines": lines, + "files": metadata.ArchiveFiles, + "shorturlEnabled": Config.googleShorterAPIKey != "", + "shorturl": metadata.ShortURL, }, r, w) if err != nil { diff --git a/meta.go b/meta.go index 7487176..df2e0b5 100644 --- a/meta.go +++ b/meta.go @@ -26,6 +26,7 @@ type MetadataJSON struct { Size int64 `json:"size"` Expiry int64 `json:"expiry"` ArchiveFiles []string `json:"archive_files,omitempty"` + ShortURL string `json:"short_url"` } type Metadata struct { @@ -35,6 +36,7 @@ type Metadata struct { Size int64 Expiry time.Time ArchiveFiles []string + ShortURL string } var NotFoundErr = errors.New("File not found.") @@ -146,6 +148,7 @@ func metadataWrite(filename string, metadata *Metadata) error { mjson.Sha256sum = metadata.Sha256sum mjson.Expiry = metadata.Expiry.Unix() mjson.Size = metadata.Size + mjson.ShortURL = metadata.ShortURL byt, err := json.Marshal(mjson) if err != nil { @@ -188,6 +191,7 @@ func metadataRead(filename string) (metadata Metadata, err error) { metadata.Sha256sum = mjson.Sha256sum metadata.Expiry = time.Unix(mjson.Expiry, 0) metadata.Size = mjson.Size + metadata.ShortURL = mjson.ShortURL return } diff --git a/server.go b/server.go index d43f941..3ff5c7f 100644 --- a/server.go +++ b/server.go @@ -55,6 +55,7 @@ var Config struct { authFile string remoteAuthFile string addHeaders headerList + googleShorterAPIKey string } var Templates = make(map[string]*pongo2.Template) @@ -145,6 +146,7 @@ func setup() *web.Mux { selifRe := regexp.MustCompile("^" + Config.sitePath + `selif/(?P[a-z0-9-\.]+)$`) selifIndexRe := regexp.MustCompile("^" + Config.sitePath + `selif/$`) torrentRe := regexp.MustCompile("^" + Config.sitePath + `(?P[a-z0-9-\.]+)/torrent$`) + shortRe := regexp.MustCompile("^" + Config.sitePath + `(?P[a-z0-9-\.]+)/short$`) if Config.authFile == "" { mux.Get(Config.sitePath, indexHandler) @@ -182,6 +184,11 @@ func setup() *web.Mux { mux.Get(selifRe, fileServeHandler) mux.Get(selifIndexRe, unauthorizedHandler) mux.Get(torrentRe, fileTorrentHandler) + + if Config.googleShorterAPIKey != "" { + mux.Get(shortRe, shortURLHandler) + } + mux.NotFound(notFoundHandler) return mux @@ -228,6 +235,8 @@ func main() { "value of X-Frame-Options header") flag.Var(&Config.addHeaders, "addheader", "Add an arbitrary header to the response. This option can be used multiple times.") + flag.StringVar(&Config.googleShorterAPIKey, "googleapikey", "", + "API Key for Google's URL Shortener.") iniflags.Parse() diff --git a/shorturl.go b/shorturl.go new file mode 100644 index 0000000..afdaf00 --- /dev/null +++ b/shorturl.go @@ -0,0 +1,89 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" + + "github.com/zenazn/goji/web" +) + +type shortenerRequest struct { + LongURL string `json:"longUrl"` +} + +type shortenerResponse struct { + Kind string `json:"kind"` + ID string `json:"id"` + LongURL string `json:"longUrl"` + Error struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error"` +} + +func shortURLHandler(c web.C, w http.ResponseWriter, r *http.Request) { + fileName := c.URLParams["name"] + + err := checkFile(fileName) + if err == NotFoundErr { + notFoundHandler(c, w, r) + return + } + + metadata, err := metadataRead(fileName) + if err != nil { + oopsHandler(c, w, r, RespJSON, "Corrupt metadata.") + return + } + + if metadata.ShortURL == "" { + url, err := shortenURL(getSiteURL(r) + fileName) + if err != nil { + oopsHandler(c, w, r, RespJSON, err.Error()) + return + } + + metadata.ShortURL = url + + err = metadataWrite(fileName, &metadata) + if err != nil { + oopsHandler(c, w, r, RespJSON, "Corrupt metadata.") + return + } + } + + js, _ := json.Marshal(map[string]string{ + "shortUrl": metadata.ShortURL, + }) + w.Write(js) + return +} + +func shortenURL(url string) (string, error) { + apiURL := "https://www.googleapis.com/urlshortener/v1/url?key=" + Config.googleShorterAPIKey + jsonStr, _ := json.Marshal(shortenerRequest{LongURL: url}) + + req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + shortenerResponse := new(shortenerResponse) + err = json.NewDecoder(resp.Body).Decode(shortenerResponse) + if err != nil { + return "", err + } + + if shortenerResponse.Error.Message != "" { + return "", errors.New(shortenerResponse.Error.Message) + } + + return shortenerResponse.ID, nil +} diff --git a/static/css/hint.css b/static/css/hint.css index 9165763..fd889cc 100644 --- a/static/css/hint.css +++ b/static/css/hint.css @@ -1,9 +1,8 @@ -/*! Hint.css - v1.3.1 - 2013-11-23 +/*! Hint.css - v2.3.1 - 2016-06-05 * http://kushagragour.in/lab/hint/ -* Copyright (c) 2013 Kushagra Gour; Licensed MIT */ +* Copyright (c) 2016 Kushagra Gour; Licensed */ -/*-------------------------------------*\ - HINT.css - A CSS tooltip library +/*-------------------------------------* HINT.css - A CSS tooltip library \*-------------------------------------*/ /** * HINT.css is a tooltip library made in pure CSS. @@ -21,20 +20,20 @@ * Each tooltip is made of 2 parts: * 1) body (:after) * 2) arrow (:before) - * + * * Classes added: * 1) hint */ -.hint, [data-hint] { +[class*="hint--"] { position: relative; display: inline-block; /** - * tooltip arrow - */ + * tooltip arrow + */ /** - * tooltip body - */ } - .hint:before, .hint:after, [data-hint]:before, [data-hint]:after { + * tooltip body + */ } + [class*="hint--"]:before, [class*="hint--"]:after { position: absolute; -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); @@ -45,26 +44,40 @@ pointer-events: none; -webkit-transition: 0.3s ease; -moz-transition: 0.3s ease; - transition: 0.3s ease; } - .hint:hover:before, .hint:hover:after, .hint:focus:before, .hint:focus:after, [data-hint]:hover:before, [data-hint]:hover:after, [data-hint]:focus:before, [data-hint]:focus:after { + transition: 0.3s ease; + -webkit-transition-delay: 0ms; + -moz-transition-delay: 0ms; + transition-delay: 0ms; } + [class*="hint--"]:hover:before, [class*="hint--"]:hover:after { visibility: visible; opacity: 1; } - .hint:before, [data-hint]:before { + [class*="hint--"]:hover:before, [class*="hint--"]:hover:after { + -webkit-transition-delay: 100ms; + -moz-transition-delay: 100ms; + transition-delay: 100ms; } + [class*="hint--"]:before { content: ''; position: absolute; background: transparent; border: 6px solid transparent; z-index: 1000001; } - .hint:after, [data-hint]:after { - content: attr(data-hint); - background: #556A7F; + [class*="hint--"]:after { + background: #383838; color: white; - text-shadow: 0 -1px 0px black; padding: 8px 10px; font-size: 12px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 12px; - white-space: nowrap; - box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); } + white-space: nowrap; } + [class*="hint--"][aria-label]:after { + content: attr(aria-label); } + [class*="hint--"][data-hint]:after { + content: attr(data-hint); } + +[aria-label='']:before, [aria-label='']:after, +[data-hint='']:before, +[data-hint='']:after { + display: none !important; } /** * source: hint-position.scss @@ -80,60 +93,106 @@ /** * set default color for tooltip arrows */ +.hint--top-left:before { + border-top-color: #383838; } + +.hint--top-right:before { + border-top-color: #383838; } + .hint--top:before { - border-top-color: #556A7F; } + border-top-color: #383838; } + +.hint--bottom-left:before { + border-bottom-color: #383838; } + +.hint--bottom-right:before { + border-bottom-color: #383838; } .hint--bottom:before { - border-bottom-color: #556A7F; } + border-bottom-color: #383838; } .hint--left:before { - border-left-color: #556A7F; } + border-left-color: #383838; } .hint--right:before { - border-right-color: #556A7F; } + border-right-color: #383838; } /** * top tooltip */ .hint--top:before { - margin-bottom: -12px; } -.hint--top:after { - margin-left: -18px; } + margin-bottom: -11px; } + .hint--top:before, .hint--top:after { bottom: 100%; left: 50%; } -.hint--top:hover:after, .hint--top:hover:before, .hint--top:focus:after, .hint--top:focus:before { + +.hint--top:before { + left: calc(50% - 6px); } + +.hint--top:after { + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + transform: translateX(-50%); } + +.hint--top:hover:before { -webkit-transform: translateY(-8px); -moz-transform: translateY(-8px); transform: translateY(-8px); } +.hint--top:hover:after { + -webkit-transform: translateX(-50%) translateY(-8px); + -moz-transform: translateX(-50%) translateY(-8px); + transform: translateX(-50%) translateY(-8px); } + /** * bottom tooltip */ .hint--bottom:before { - margin-top: -12px; } -.hint--bottom:after { - margin-left: -18px; } + margin-top: -11px; } + .hint--bottom:before, .hint--bottom:after { top: 100%; left: 50%; } -.hint--bottom:hover:after, .hint--bottom:hover:before, .hint--bottom:focus:after, .hint--bottom:focus:before { + +.hint--bottom:before { + left: calc(50% - 6px); } + +.hint--bottom:after { + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + transform: translateX(-50%); } + +.hint--bottom:hover:before { -webkit-transform: translateY(8px); -moz-transform: translateY(8px); transform: translateY(8px); } +.hint--bottom:hover:after { + -webkit-transform: translateX(-50%) translateY(8px); + -moz-transform: translateX(-50%) translateY(8px); + transform: translateX(-50%) translateY(8px); } + /** * right tooltip */ .hint--right:before { - margin-left: -12px; + margin-left: -11px; margin-bottom: -6px; } + .hint--right:after { margin-bottom: -14px; } + .hint--right:before, .hint--right:after { left: 100%; bottom: 50%; } -.hint--right:hover:after, .hint--right:hover:before, .hint--right:focus:after, .hint--right:focus:before { + +.hint--right:hover:before { + -webkit-transform: translateX(8px); + -moz-transform: translateX(8px); + transform: translateX(8px); } + +.hint--right:hover:after { -webkit-transform: translateX(8px); -moz-transform: translateX(8px); transform: translateX(8px); } @@ -142,18 +201,191 @@ * left tooltip */ .hint--left:before { - margin-right: -12px; + margin-right: -11px; margin-bottom: -6px; } + .hint--left:after { margin-bottom: -14px; } + .hint--left:before, .hint--left:after { right: 100%; bottom: 50%; } -.hint--left:hover:after, .hint--left:hover:before, .hint--left:focus:after, .hint--left:focus:before { + +.hint--left:hover:before { -webkit-transform: translateX(-8px); -moz-transform: translateX(-8px); transform: translateX(-8px); } +.hint--left:hover:after { + -webkit-transform: translateX(-8px); + -moz-transform: translateX(-8px); + transform: translateX(-8px); } + +/** + * top-left tooltip + */ +.hint--top-left:before { + margin-bottom: -11px; } + +.hint--top-left:before, .hint--top-left:after { + bottom: 100%; + left: 50%; } + +.hint--top-left:before { + left: calc(50% - 6px); } + +.hint--top-left:after { + -webkit-transform: translateX(-100%); + -moz-transform: translateX(-100%); + transform: translateX(-100%); } + +.hint--top-left:after { + margin-left: 12px; } + +.hint--top-left:hover:before { + -webkit-transform: translateY(-8px); + -moz-transform: translateY(-8px); + transform: translateY(-8px); } + +.hint--top-left:hover:after { + -webkit-transform: translateX(-100%) translateY(-8px); + -moz-transform: translateX(-100%) translateY(-8px); + transform: translateX(-100%) translateY(-8px); } + +/** + * top-right tooltip + */ +.hint--top-right:before { + margin-bottom: -11px; } + +.hint--top-right:before, .hint--top-right:after { + bottom: 100%; + left: 50%; } + +.hint--top-right:before { + left: calc(50% - 6px); } + +.hint--top-right:after { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + transform: translateX(0); } + +.hint--top-right:after { + margin-left: -12px; } + +.hint--top-right:hover:before { + -webkit-transform: translateY(-8px); + -moz-transform: translateY(-8px); + transform: translateY(-8px); } + +.hint--top-right:hover:after { + -webkit-transform: translateY(-8px); + -moz-transform: translateY(-8px); + transform: translateY(-8px); } + +/** + * bottom-left tooltip + */ +.hint--bottom-left:before { + margin-top: -11px; } + +.hint--bottom-left:before, .hint--bottom-left:after { + top: 100%; + left: 50%; } + +.hint--bottom-left:before { + left: calc(50% - 6px); } + +.hint--bottom-left:after { + -webkit-transform: translateX(-100%); + -moz-transform: translateX(-100%); + transform: translateX(-100%); } + +.hint--bottom-left:after { + margin-left: 12px; } + +.hint--bottom-left:hover:before { + -webkit-transform: translateY(8px); + -moz-transform: translateY(8px); + transform: translateY(8px); } + +.hint--bottom-left:hover:after { + -webkit-transform: translateX(-100%) translateY(8px); + -moz-transform: translateX(-100%) translateY(8px); + transform: translateX(-100%) translateY(8px); } + +/** + * bottom-right tooltip + */ +.hint--bottom-right:before { + margin-top: -11px; } + +.hint--bottom-right:before, .hint--bottom-right:after { + top: 100%; + left: 50%; } + +.hint--bottom-right:before { + left: calc(50% - 6px); } + +.hint--bottom-right:after { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + transform: translateX(0); } + +.hint--bottom-right:after { + margin-left: -12px; } + +.hint--bottom-right:hover:before { + -webkit-transform: translateY(8px); + -moz-transform: translateY(8px); + transform: translateY(8px); } + +.hint--bottom-right:hover:after { + -webkit-transform: translateY(8px); + -moz-transform: translateY(8px); + transform: translateY(8px); } + +/** + * source: hint-sizes.scss + * + * Defines width restricted tooltips that can span + * across multiple lines. + * + * Classes added: + * 1) hint--small + * 2) hint--medium + * 3) hint--large + * + */ +.hint--small:after, +.hint--medium:after, +.hint--large:after { + white-space: normal; + line-height: 1.4em; } + +.hint--small:after { + width: 80px; } + +.hint--medium:after { + width: 150px; } + +.hint--large:after { + width: 300px; } + +/** + * source: hint-theme.scss + * + * Defines basic theme for tooltips. + * + */ +[class*="hint--"] { + /** + * tooltip body + */ } + [class*="hint--"]:after { + text-shadow: 0 -1px 0px black; + box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); } + /** * source: hint-color-types.scss * @@ -172,12 +404,28 @@ .hint--error:after { background-color: #b34e4d; text-shadow: 0 -1px 0px #592726; } + +.hint--error.hint--top-left:before { + border-top-color: #b34e4d; } + +.hint--error.hint--top-right:before { + border-top-color: #b34e4d; } + .hint--error.hint--top:before { border-top-color: #b34e4d; } + +.hint--error.hint--bottom-left:before { + border-bottom-color: #b34e4d; } + +.hint--error.hint--bottom-right:before { + border-bottom-color: #b34e4d; } + .hint--error.hint--bottom:before { border-bottom-color: #b34e4d; } + .hint--error.hint--left:before { border-left-color: #b34e4d; } + .hint--error.hint--right:before { border-right-color: #b34e4d; } @@ -187,12 +435,28 @@ .hint--warning:after { background-color: #c09854; text-shadow: 0 -1px 0px #6c5328; } + +.hint--warning.hint--top-left:before { + border-top-color: #c09854; } + +.hint--warning.hint--top-right:before { + border-top-color: #c09854; } + .hint--warning.hint--top:before { border-top-color: #c09854; } + +.hint--warning.hint--bottom-left:before { + border-bottom-color: #c09854; } + +.hint--warning.hint--bottom-right:before { + border-bottom-color: #c09854; } + .hint--warning.hint--bottom:before { border-bottom-color: #c09854; } + .hint--warning.hint--left:before { border-left-color: #c09854; } + .hint--warning.hint--right:before { border-right-color: #c09854; } @@ -201,13 +465,29 @@ */ .hint--info:after { background-color: #3986ac; - text-shadow: 0 -1px 0px #193b4d; } + text-shadow: 0 -1px 0px #1a3c4d; } + +.hint--info.hint--top-left:before { + border-top-color: #3986ac; } + +.hint--info.hint--top-right:before { + border-top-color: #3986ac; } + .hint--info.hint--top:before { border-top-color: #3986ac; } + +.hint--info.hint--bottom-left:before { + border-bottom-color: #3986ac; } + +.hint--info.hint--bottom-right:before { + border-bottom-color: #3986ac; } + .hint--info.hint--bottom:before { border-bottom-color: #3986ac; } + .hint--info.hint--left:before { border-left-color: #3986ac; } + .hint--info.hint--right:before { border-right-color: #3986ac; } @@ -217,12 +497,28 @@ .hint--success:after { background-color: #458746; text-shadow: 0 -1px 0px #1a321a; } + +.hint--success.hint--top-left:before { + border-top-color: #458746; } + +.hint--success.hint--top-right:before { + border-top-color: #458746; } + .hint--success.hint--top:before { border-top-color: #458746; } + +.hint--success.hint--bottom-left:before { + border-bottom-color: #458746; } + +.hint--success.hint--bottom-right:before { + border-bottom-color: #458746; } + .hint--success.hint--bottom:before { border-bottom-color: #458746; } + .hint--success.hint--left:before { border-left-color: #458746; } + .hint--success.hint--right:before { border-right-color: #458746; } @@ -238,19 +534,83 @@ .hint--always:after, .hint--always:before { opacity: 1; visibility: visible; } -.hint--always.hint--top:after, .hint--always.hint--top:before { + +.hint--always.hint--top:before { -webkit-transform: translateY(-8px); -moz-transform: translateY(-8px); transform: translateY(-8px); } -.hint--always.hint--bottom:after, .hint--always.hint--bottom:before { + +.hint--always.hint--top:after { + -webkit-transform: translateX(-50%) translateY(-8px); + -moz-transform: translateX(-50%) translateY(-8px); + transform: translateX(-50%) translateY(-8px); } + +.hint--always.hint--top-left:before { + -webkit-transform: translateY(-8px); + -moz-transform: translateY(-8px); + transform: translateY(-8px); } + +.hint--always.hint--top-left:after { + -webkit-transform: translateX(-100%) translateY(-8px); + -moz-transform: translateX(-100%) translateY(-8px); + transform: translateX(-100%) translateY(-8px); } + +.hint--always.hint--top-right:before { + -webkit-transform: translateY(-8px); + -moz-transform: translateY(-8px); + transform: translateY(-8px); } + +.hint--always.hint--top-right:after { + -webkit-transform: translateY(-8px); + -moz-transform: translateY(-8px); + transform: translateY(-8px); } + +.hint--always.hint--bottom:before { -webkit-transform: translateY(8px); -moz-transform: translateY(8px); transform: translateY(8px); } -.hint--always.hint--left:after, .hint--always.hint--left:before { + +.hint--always.hint--bottom:after { + -webkit-transform: translateX(-50%) translateY(8px); + -moz-transform: translateX(-50%) translateY(8px); + transform: translateX(-50%) translateY(8px); } + +.hint--always.hint--bottom-left:before { + -webkit-transform: translateY(8px); + -moz-transform: translateY(8px); + transform: translateY(8px); } + +.hint--always.hint--bottom-left:after { + -webkit-transform: translateX(-100%) translateY(8px); + -moz-transform: translateX(-100%) translateY(8px); + transform: translateX(-100%) translateY(8px); } + +.hint--always.hint--bottom-right:before { + -webkit-transform: translateY(8px); + -moz-transform: translateY(8px); + transform: translateY(8px); } + +.hint--always.hint--bottom-right:after { + -webkit-transform: translateY(8px); + -moz-transform: translateY(8px); + transform: translateY(8px); } + +.hint--always.hint--left:before { -webkit-transform: translateX(-8px); -moz-transform: translateX(-8px); transform: translateX(-8px); } -.hint--always.hint--right:after, .hint--always.hint--right:before { + +.hint--always.hint--left:after { + -webkit-transform: translateX(-8px); + -moz-transform: translateX(-8px); + transform: translateX(-8px); } + +.hint--always.hint--right:before { + -webkit-transform: translateX(8px); + -moz-transform: translateX(8px); + transform: translateX(8px); } + +.hint--always.hint--right:after { -webkit-transform: translateX(8px); -moz-transform: translateX(8px); transform: translateX(8px); } @@ -273,9 +633,15 @@ * Defines various transition effects for the tooltips. * * Classes added: - * 1) hint--bounce + * 1) hint--no-animate + * 2) hint--bounce * */ +.hint--no-animate:before, .hint--no-animate:after { + -webkit-transition-duration: 0ms; + -moz-transition-duration: 0ms; + transition-duration: 0ms; } + .hint--bounce:before, .hint--bounce:after { -webkit-transition: opacity 0.3s ease, visibility 0.3s ease, -webkit-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24); -moz-transition: opacity 0.3s ease, visibility 0.3s ease, -moz-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24); diff --git a/static/js/clipboard.js b/static/js/clipboard.js new file mode 100644 index 0000000..e54411e --- /dev/null +++ b/static/js/clipboard.js @@ -0,0 +1,740 @@ +/*! + * clipboard.js v1.5.10 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o{% block title %}{{ sitename }}{% endblock %} + {% block head %}{% endblock %} diff --git a/templates/display/base.html b/templates/display/base.html index 1c595b8..587e76f 100644 --- a/templates/display/base.html +++ b/templates/display/base.html @@ -16,7 +16,16 @@ file expires in {{ expiry }} | {% endif %} {% block infomore %}{% endblock %} - {{ size }} | + {{ size }} | + {% if shorturlEnabled %} + {% if shorturl %} + {{shorturl}} | + {% else %} + short url | + {% endif %} + {% endif %} torrent | get @@ -32,4 +41,10 @@ + + + + {% if shorturlEnabled %} + + {% endif %} {% endblock %} diff --git a/templates/paste.html b/templates/paste.html index 3d71c74..1294b5e 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -1,9 +1,5 @@ {% extends "base.html" %} -{% block head %} - -{% endblock %} - {% block content %}
From e6db8553b0e0607faa5cd9d9bdefa5fd7dd8fe4f Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Thu, 16 Jun 2016 22:33:32 -0700 Subject: [PATCH 018/233] Fix line numbers for text --- templates/display/bin.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/display/bin.html b/templates/display/bin.html index a3aab36..12e49c9 100644 --- a/templates/display/bin.html +++ b/templates/display/bin.html @@ -2,8 +2,10 @@ {% block head %} +{% if extra.lang_hl != "text" %} - {% endblock %} +{% endif %} +{% endblock %} {% block innercontentmore %} class="scrollable"{% endblock %} From 9467be97179faae97ea1810062c0119258eeaee5 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Fri, 17 Jun 2016 21:22:24 -0700 Subject: [PATCH 019/233] No margin/border without line numbers --- static/css/highlight/lines.css | 6 +++--- static/js/bin_hljs.js | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/static/css/highlight/lines.css b/static/css/highlight/lines.css index 68c7a56..107e272 100644 --- a/static/css/highlight/lines.css +++ b/static/css/highlight/lines.css @@ -1,14 +1,14 @@ -#normal-code { +.linenumbers { border-left: 1px solid #ccc; margin-left: 36px; position: relative; } -#normal-code code { +.linenumbers code { counter-reset: linenumbers; } -#normal-code code div:before { +.linenumbers code div:before { content: counter(linenumbers); counter-increment: linenumbers; left: -36px; diff --git a/static/js/bin_hljs.js b/static/js/bin_hljs.js index 083ef4c..bf11657 100644 --- a/static/js/bin_hljs.js +++ b/static/js/bin_hljs.js @@ -12,4 +12,7 @@ for (var i = 0; i < lines.length; i++) { codeb.appendChild(div); }; + +var ncode = document.getElementById("normal-code"); +ncode.className = "linenumbers"; // @license-end From 93d37002ddf2793bf5c3a802f1803375c80f7ce7 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Thu, 7 Jul 2016 19:34:53 -0700 Subject: [PATCH 020/233] remove unnecessary file processing in display.go There is no need to open the file here; nothing is done with the header after it is read. --- display.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/display.go b/display.go index f26b665..b196477 100644 --- a/display.go +++ b/display.go @@ -39,12 +39,6 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { extra := make(map[string]string) lines := []string{} - file, _ := fileBackend.Open(fileName) - defer file.Close() - - header := make([]byte, 512) - file.Read(header) - extension := strings.TrimPrefix(filepath.Ext(fileName), ".") if strings.EqualFold("application/json", r.Header.Get("Accept")) { From 1e1f28658d6febdb5426379547c34c8248befdd8 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Fri, 22 Jul 2016 18:08:59 -0700 Subject: [PATCH 021/233] Remove spaces in mime mapping and ensure no spaces in filenames --- upload.go | 1 + util.go | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/upload.go b/upload.go index b8b395b..8c8f9f6 100644 --- a/upload.go +++ b/upload.go @@ -230,6 +230,7 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { } upload.Filename = strings.Join([]string{barename, extension}, ".") + upload.Filename = strings.Replace(upload.Filename, " ", "", -1) fileexists, _ := fileBackend.Exists(upload.Filename) diff --git a/util.go b/util.go index 37c59a9..c07a5f4 100644 --- a/util.go +++ b/util.go @@ -44,7 +44,7 @@ var mimeToExtension = map[string]string{ "application/gpx+xml": "gpx", "application/gxf": "gxf", "application/hyperstudio": "stk", - "application/inkml+xml": "ink inkml", + "application/inkml+xml": "ink", "application/ipfix": "ipfix", "application/java-archive": "jar", "application/java-serialized-object": "ser", @@ -135,7 +135,7 @@ var mimeToExtension = map[string]string{ "application/vnd.accpac.simply.aso": "aso", "application/vnd.accpac.simply.imp": "imp", "application/vnd.acucobol": "acu", - "application/vnd.acucorp": "atc acutc", + "application/vnd.acucorp": "atc", "application/vnd.adobe.air-application-installer-package+zip": "air", "application/vnd.adobe.formscentral.fcdt": "fcdt", "application/vnd.adobe.fxp": "fxp", @@ -226,14 +226,14 @@ var mimeToExtension = map[string]string{ "application/vnd.genomatix.tuxedo": "txd", "application/vnd.geogebra.file": "ggb", "application/vnd.geogebra.tool": "ggt", - "application/vnd.geometry-explorer": "gex gre", + "application/vnd.geometry-explorer": "gex", "application/vnd.geonext": "gxt", "application/vnd.geoplan": "g2w", "application/vnd.geospace": "g3w", "application/vnd.gmx": "gmx", "application/vnd.google-earth.kml+xml": "kml", "application/vnd.google-earth.kmz": "kmz", - "application/vnd.grafeq": "gqf gqs", + "application/vnd.grafeq": "gqf", "application/vnd.groove-account": "gac", "application/vnd.groove-help": "ghf", "application/vnd.groove-identity-message": "gim", @@ -256,7 +256,7 @@ var mimeToExtension = map[string]string{ "application/vnd.ibm.modcap": "afp", "application/vnd.ibm.rights-management": "irm", "application/vnd.ibm.secure-container": "sc", - "application/vnd.iccprofile": "icc icm", + "application/vnd.iccprofile": "icc", "application/vnd.igloader": "igl", "application/vnd.immervision-ivp": "ivp", "application/vnd.immervision-ivu": "ivu", @@ -284,7 +284,7 @@ var mimeToExtension = map[string]string{ "application/vnd.kde.kword": "kwd", "application/vnd.kenameaapp": "htke", "application/vnd.kidspiration": "kia", - "application/vnd.kinar": "kne knp", + "application/vnd.kinar": "kne", "application/vnd.koan": "skp", "application/vnd.kodak-descriptor": "sse", "application/vnd.las.las+xml": "lasxml", @@ -330,13 +330,13 @@ var mimeToExtension = map[string]string{ "application/vnd.ms-officetheme": "thmx", "application/vnd.ms-pki.seccat": "cat", "application/vnd.ms-pki.stl": "stl", - "application/vnd.ms-powerpoint": "ppt pps pot", + "application/vnd.ms-powerpoint": "ppt", "application/vnd.ms-powerpoint.addin.macroenabled.12": "ppam", "application/vnd.ms-powerpoint.presentation.macroenabled.12": "pptm", "application/vnd.ms-powerpoint.slide.macroenabled.12": "sldm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12": "ppsm", "application/vnd.ms-powerpoint.template.macroenabled.12": "potm", - "application/vnd.ms-project": "mpp mpt", + "application/vnd.ms-project": "mpp", "application/vnd.ms-word.document.macroenabled.12": "docm", "application/vnd.ms-word.template.macroenabled.12": "dotm", "application/vnd.ms-works": "wps", @@ -347,7 +347,7 @@ var mimeToExtension = map[string]string{ "application/vnd.muvee.style": "msty", "application/vnd.mynfc": "taglet", "application/vnd.neurolanguage.nlu": "nlu", - "application/vnd.nitf": "ntf nitf", + "application/vnd.nitf": "ntf", "application/vnd.noblenet-directory": "nnd", "application/vnd.noblenet-sealer": "nns", "application/vnd.noblenet-web": "nnw", @@ -389,7 +389,7 @@ var mimeToExtension = map[string]string{ "application/vnd.osgeo.mapguide.package": "mgp", "application/vnd.osgi.dp": "dp", "application/vnd.osgi.subsystem": "esa", - "application/vnd.palm": "pdb pqa oprc", + "application/vnd.palm": "pdb", "application/vnd.pawaafile": "paw", "application/vnd.pg.format": "str", "application/vnd.pg.osasli": "ei6", @@ -655,8 +655,8 @@ var mimeToExtension = map[string]string{ "image/png": "png", "image/prs.btif": "btif", "image/sgi": "sgi", - "image/svg+xml": "svg svgz", - "image/tiff": "tiff tif", + "image/svg+xml": "svg", + "image/tiff": "tiff", "image/vnd.adobe.photoshop": "psd", "image/vnd.dece.graphic": "uvi", "image/vnd.dvb.subtitle": "sub", @@ -691,7 +691,7 @@ var mimeToExtension = map[string]string{ "image/x-xbitmap": "xbm", "image/x-xpixmap": "xpm", "image/x-xwindowdump": "xwd", - "message/rfc822": "eml mime", + "message/rfc822": "eml", "model/iges": "igs", "model/mesh": "mesh", "model/vnd.collada+xml": "dae", @@ -700,12 +700,12 @@ var mimeToExtension = map[string]string{ "model/vnd.gtw": "gtw", "model/vnd.mts": "mts", "model/vnd.vtu": "vtu", - "model/vrml": "wrl vrml", + "model/vrml": "wrl", "model/x3d+binary": "x3db", "model/x3d+vrml": "x3dv", "model/x3d+xml": "x3d", "text/cache-manifest": "appcache", - "text/calendar": "ics ifb", + "text/calendar": "ics", "text/css": "css", "text/csv": "csv", "text/html": "html", @@ -713,7 +713,7 @@ var mimeToExtension = map[string]string{ "text/plain": "txt", "text/prs.lines.tag": "dsc", "text/richtext": "rtx", - "text/sgml": "sgml sgm", + "text/sgml": "sgml", "text/tab-separated-values": "tsv", "text/troff": "t", "text/turtle": "ttl", @@ -732,12 +732,12 @@ var mimeToExtension = map[string]string{ "text/vnd.sun.j2me.app-descriptor": "jad", "text/vnd.wap.wml": "wml", "text/vnd.wap.wmlscript": "wmls", - "text/x-asm": "s asm", + "text/x-asm": "s", "text/x-c": "c", "text/x-fortran": "f", "text/x-java-source": "java", "text/x-opml": "opml", - "text/x-pascal": "p pas", + "text/x-pascal": "p", "text/x-nfo": "nfo", "text/x-shellscript": "sh", "text/x-setext": "etx", From 81a15138094ee3ec5b48ecb03737bdf73496a064 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Fri, 22 Jul 2016 18:15:44 -0700 Subject: [PATCH 022/233] Add newline for PUT response --- server_test.go | 3 ++- upload.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server_test.go b/server_test.go index 527e3be..ed45105 100644 --- a/server_test.go +++ b/server_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/tls" "encoding/json" + "fmt" "mime/multipart" "net/http" "net/http/httptest" @@ -611,7 +612,7 @@ func TestPutUpload(t *testing.T) { mux.ServeHTTP(w, req) - if w.Body.String() != Config.siteURL+filename { + if w.Body.String() != fmt.Sprintf("%s\n", Config.siteURL+filename) { t.Fatal("Response was not expected URL") } } diff --git a/upload.go b/upload.go index 8c8f9f6..b0bbd9f 100644 --- a/upload.go +++ b/upload.go @@ -132,7 +132,7 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } - fmt.Fprintf(w, getSiteURL(r)+upload.Filename) + fmt.Fprintf(w, "%s\n", getSiteURL(r)+upload.Filename) } } From 4ef3f338215229a54811099c7b2444cebebc23b2 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Mon, 22 Aug 2016 23:02:46 +0200 Subject: [PATCH 023/233] Add systemd-service (#96) --- linx-server.service | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 linx-server.service diff --git a/linx-server.service b/linx-server.service new file mode 100644 index 0000000..d764832 --- /dev/null +++ b/linx-server.service @@ -0,0 +1,8 @@ +[Unit] +Description=Self-hosted file/code/media sharing website +After=network.target + +[Service] +User=linx +Group=linx +ExecStart=/usr/bin/linx-server From 2e5521ce7dde35aef0beda44b3ecbba78fa15934 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Thu, 15 Sep 2016 22:26:35 -0700 Subject: [PATCH 024/233] Add Dockerfile This will build the binary directly in the image without a need to have Go installed locally. --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6ff81bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:alpine + +RUN set -ex \ + && apk add --no-cache --virtual .build-deps git mercurial \ + && go get github.com/andreimarcu/linx-server \ + && apk del .build-deps + +VOLUME ["/data/files", "/data/meta"] + +EXPOSE 8080 +USER nobody +ENTRYPOINT ["/go/bin/linx-server", "-bind=0.0.0.0:8080", "-filespath=/data/files/", "-metapath=/data/meta/"] +CMD ["-sitename=linx", "-allowhotlink"] From 932a470ead2aa7ebc19a62f68339f28c11adbc0b Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sat, 17 Sep 2016 20:20:46 -0700 Subject: [PATCH 025/233] add Docker instructions to readme --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index af844e3..780384d 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,15 @@ Run linx-server with the ```-certfile path/to/cert.file``` and ```-keyfile path/ #### 3. Using the built-in http server Run linx-server normally. +#### 4. Using Docker with the built-in http server +First, build the image: +```docker build -t linx-server .``` + +You'll need some directories for the persistent storage. For the purposes of this example, we will use `/media/meta` and `/media/files`. + +Then, run it: +```docker run -p 8080:8080 -v /media/meta:/data/meta -v /media/files:/data/files linx-server``` + Development ----------- From fef43d856e115dda758a822964d7596e66640d71 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sun, 18 Sep 2016 21:45:00 -0700 Subject: [PATCH 026/233] Add option for maximum expiration time (fixes #99) --- README.md | 1 + expiry.go | 53 +++++++++++++++++++++++++ pages.go | 7 +++- server.go | 3 ++ server_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++++ templates/index.html | 11 ++---- templates/paste.html | 14 ++----- upload.go | 9 +++-- 8 files changed, 169 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 780384d..59b978b 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ allowhotlink = true - ```-filespath files/"``` -- Path to store uploads (default is files/) - ```-metapath meta/``` -- Path to store information about uploads (default is meta/) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) +- ```-maxexpiry 86400``` -- maximum expiration time in seconds (default is 0, which is no expiry) - ```-allowhotlink``` -- Allow file hotlinking - ```-contentsecuritypolicy "..."``` -- Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") - ```-filecontentsecuritypolicy "..."``` -- Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") diff --git a/expiry.go b/expiry.go index 9deaf72..4172f51 100644 --- a/expiry.go +++ b/expiry.go @@ -2,8 +2,25 @@ package main import ( "time" + + "github.com/dustin/go-humanize" ) +var defaultExpiryList = []uint64{ + 60, + 300, + 3600, + 86400, + 604800, + 2419200, + 31536000, +} + +type ExpirationTime struct { + Seconds uint64 + Human string +} + var neverExpire = time.Unix(0, 0) // Determine if a file with expiry set to "ts" has expired yet @@ -21,3 +38,39 @@ func isFileExpired(filename string) (bool, error) { return isTsExpired(metadata.Expiry), nil } + +// Return a list of expiration times and their humanized versions +func listExpirationTimes() []ExpirationTime { + epoch := time.Now() + actualExpiryInList := false + var expiryList []ExpirationTime + + for _, expiry := range defaultExpiryList { + if Config.maxExpiry == 0 || expiry <= Config.maxExpiry { + if expiry == Config.maxExpiry { + actualExpiryInList = true + } + + duration := time.Duration(expiry) * time.Second + expiryList = append(expiryList, ExpirationTime{ + expiry, + humanize.RelTime(epoch, epoch.Add(duration), "", ""), + }) + } + } + + if Config.maxExpiry == 0 { + expiryList = append(expiryList, ExpirationTime{ + 0, + "never", + }) + } else if actualExpiryInList == false { + duration := time.Duration(Config.maxExpiry) * time.Second + expiryList = append(expiryList, ExpirationTime{ + Config.maxExpiry, + humanize.RelTime(epoch, epoch.Add(duration), "", ""), + }) + } + + return expiryList +} diff --git a/pages.go b/pages.go index abd2cbd..f58fa88 100644 --- a/pages.go +++ b/pages.go @@ -21,7 +21,8 @@ const ( func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { err := renderTemplate(Templates["index.html"], pongo2.Context{ - "maxsize": Config.maxSize, + "maxsize": Config.maxSize, + "expirylist": listExpirationTimes(), }, r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -29,7 +30,9 @@ func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { } func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) { - err := renderTemplate(Templates["paste.html"], pongo2.Context{}, r, w) + err := renderTemplate(Templates["paste.html"], pongo2.Context{ + "expirylist": listExpirationTimes(), + }, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") } diff --git a/server.go b/server.go index 3ff5c7f..ac229fe 100644 --- a/server.go +++ b/server.go @@ -47,6 +47,7 @@ var Config struct { fileContentSecurityPolicy string xFrameOptions string maxSize int64 + maxExpiry uint64 realIp bool noLogs bool allowHotlink bool @@ -211,6 +212,8 @@ func main() { "site base url (including trailing slash)") flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024, "maximum upload file size in bytes (default 4GB)") + flag.Uint64Var(&Config.maxExpiry, "maxexpiry", 0, + "maximum expiration time in seconds (default is 0, which is no expiry)") flag.StringVar(&Config.certFile, "certfile", "", "path to ssl certificate (for https)") flag.StringVar(&Config.keyFile, "keyfile", "", diff --git a/server_test.go b/server_test.go index ed45105..af7abe0 100644 --- a/server_test.go +++ b/server_test.go @@ -54,6 +54,44 @@ func TestIndex(t *testing.T) { } } +func TestIndexStandardMaxExpiry(t *testing.T) { + mux := setup() + Config.maxExpiry = 60 + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if strings.Contains(w.Body.String(), ">1 hour") { + t.Fatal("String '>1 hour' found in index response") + } + + Config.maxExpiry = 0 +} + +func TestIndexWeirdMaxExpiry(t *testing.T) { + mux := setup() + Config.maxExpiry = 1500 + w := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if strings.Contains(w.Body.String(), ">never") { + t.Fatal("String '>never' found in index response") + } + + Config.maxExpiry = 0 +} + func TestAddHeader(t *testing.T) { Config.addHeaders = []string{"Linx-Test: It works!"} @@ -408,6 +446,62 @@ func TestPostJSONUpload(t *testing.T) { } } +func TestPostJSONUploadMaxExpiry(t *testing.T) { + mux := setup() + Config.maxExpiry = 300 + + testExpiries := []string{"86400", "-150"} + for _, expiry := range testExpiries { + w := httptest.NewRecorder() + + filename := generateBarename() + ".txt" + + var b bytes.Buffer + mw := multipart.NewWriter(&b) + fw, err := mw.CreateFormFile("file", filename) + if err != nil { + t.Fatal(err) + } + + fw.Write([]byte("File content")) + mw.Close() + + req, err := http.NewRequest("POST", "/upload/", &b) + req.Header.Set("Content-Type", mw.FormDataContentType()) + req.Header.Set("Accept", "application/json") + req.Header.Set("Linx-Expiry", expiry) + if err != nil { + t.Fatal(err) + } + + mux.ServeHTTP(w, req) + + if w.Code != 200 { + t.Log(w.Body.String()) + t.Fatalf("Status code is not 200, but %d", w.Code) + } + + var myjson RespOkJSON + err = json.Unmarshal([]byte(w.Body.String()), &myjson) + if err != nil { + fmt.Println(w.Body.String()) + t.Fatal(err) + } + + myExp, err := strconv.ParseInt(myjson.Expiry, 10, 64) + if err != nil { + t.Fatal(err) + } + + expected := time.Now().Add(time.Duration(Config.maxExpiry) * time.Second).Unix() + if myExp != expected { + t.Fatalf("File expiry is not %d but %s", expected, myjson.Expiry) + } + } + + Config.maxExpiry = 0 +} + func TestPostExpiresJSONUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() diff --git a/templates/index.html b/templates/index.html index 240a17d..5e95d01 100644 --- a/templates/index.html +++ b/templates/index.html @@ -20,14 +20,9 @@
diff --git a/templates/paste.html b/templates/paste.html index 1294b5e..9178ba4 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -8,19 +8,13 @@
-
diff --git a/upload.go b/upload.go index b0bbd9f..e67a20a 100644 --- a/upload.go +++ b/upload.go @@ -343,12 +343,15 @@ func barePlusExt(filename string) (barename, extension string) { func parseExpiry(expStr string) time.Duration { if expStr == "" { - return 0 + return time.Duration(Config.maxExpiry) * time.Second } else { - expiry, err := strconv.ParseInt(expStr, 10, 64) + expiry, err := strconv.ParseUint(expStr, 10, 64) if err != nil { - return 0 + return time.Duration(Config.maxExpiry) * time.Second } else { + if Config.maxExpiry > 0 && expiry > Config.maxExpiry { + expiry = Config.maxExpiry + } return time.Duration(expiry) * time.Second } } From eb036f28199204ab32f841d37e945d402694baf4 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Sun, 18 Sep 2016 22:13:52 -0700 Subject: [PATCH 027/233] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59b978b..1921855 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ allowhotlink = true - ```-bind 127.0.0.1:8080``` -- what to bind to (default is 127.0.0.1:8080) - ```-sitename myLinx``` -- the site name displayed on top (default is inferred from Host header) - ```-siteurl "http://mylinx.example.org/"``` -- the site url (default is inferred from execution context) -- ```-filespath files/"``` -- Path to store uploads (default is files/) +- ```-filespath files/``` -- Path to store uploads (default is files/) - ```-metapath meta/``` -- Path to store information about uploads (default is meta/) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) - ```-maxexpiry 86400``` -- maximum expiration time in seconds (default is 0, which is no expiry) From c75cd5ef9efcf56f9c8909718daa4e400db837e4 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 9 Oct 2016 14:56:26 +0200 Subject: [PATCH 028/233] Add Install-section to systemd-service --- linx-server.service | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linx-server.service b/linx-server.service index d764832..13b1465 100644 --- a/linx-server.service +++ b/linx-server.service @@ -6,3 +6,6 @@ After=network.target User=linx Group=linx ExecStart=/usr/bin/linx-server + +[Install] +WantedBy=multi-user.target From 8f384dc5ceac7ee72b2bfd8c0bf284251ee98a0f Mon Sep 17 00:00:00 2001 From: culdev Date: Sun, 23 Oct 2016 21:07:47 +0200 Subject: [PATCH 029/233] Use value instead of selectedOptions. --- static/js/upload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/upload.js b/static/js/upload.js index 6ebcfa6..5e54d62 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -43,7 +43,7 @@ Dropzone.options.dropzone = { }, sending: function(file, xhr, formData) { formData.append("randomize", document.getElementById("randomize").checked); - formData.append("expires", document.getElementById("expires").selectedOptions[0].value); + formData.append("expires", document.getElementById("expires").value); }, success: function(file, resp) { file.fileActions.removeChild(file.progressElement); From 647aa2c0f612cde385a2efb7150d05ea5f442533 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Wed, 2 Nov 2016 19:31:32 -0700 Subject: [PATCH 030/233] Fix max expiry when provided expiry is 0 Previously, we did not properly handle the case where the provided expiry was zero and the max expiry was configured to be nonzero; add an additional check to cover this case. Fixes #111. --- server_test.go | 4 +++- upload.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server_test.go b/server_test.go index af7abe0..a590465 100644 --- a/server_test.go +++ b/server_test.go @@ -450,7 +450,9 @@ func TestPostJSONUploadMaxExpiry(t *testing.T) { mux := setup() Config.maxExpiry = 300 - testExpiries := []string{"86400", "-150"} + // include 0 to test edge case + // https://github.com/andreimarcu/linx-server/issues/111 + testExpiries := []string{"86400", "-150", "0"} for _, expiry := range testExpiries { w := httptest.NewRecorder() diff --git a/upload.go b/upload.go index e67a20a..1233d1a 100644 --- a/upload.go +++ b/upload.go @@ -349,7 +349,7 @@ func parseExpiry(expStr string) time.Duration { if err != nil { return time.Duration(Config.maxExpiry) * time.Second } else { - if Config.maxExpiry > 0 && expiry > Config.maxExpiry { + if Config.maxExpiry > 0 && (expiry > Config.maxExpiry || expiry == 0) { expiry = Config.maxExpiry } return time.Duration(expiry) * time.Second From e6ac89d6dc1b32164617b424689c12a21eaeafb3 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sat, 25 Mar 2017 01:05:23 -0700 Subject: [PATCH 031/233] Switch to https://github.com/h2non/filetype This library is much better at detecting MIME types properly than the existing one. Fixes #117. --- meta.go | 9 +++++++-- upload.go | 13 +++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/meta.go b/meta.go index df2e0b5..d2fb637 100644 --- a/meta.go +++ b/meta.go @@ -15,8 +15,8 @@ import ( "time" "unicode" - "bitbucket.org/taruti/mimemagic" "github.com/dchest/uniuri" + "gopkg.in/h2non/filetype.v1" ) type MetadataJSON struct { @@ -66,7 +66,12 @@ func generateMetadata(fName string, exp time.Time, delKey string) (m Metadata, e header := make([]byte, 512) file.Read(header) - m.Mimetype = mimemagic.Match("", header) + kind, err := filetype.Match(header) + if err != nil { + m.Mimetype = "application/octet-stream" + } else { + m.Mimetype = kind.MIME.Value + } if m.Mimetype == "" { // Check if the file seems anything like text diff --git a/upload.go b/upload.go index 1233d1a..bd2bed6 100644 --- a/upload.go +++ b/upload.go @@ -15,9 +15,9 @@ import ( "strings" "time" - "bitbucket.org/taruti/mimemagic" "github.com/dchest/uniuri" "github.com/zenazn/goji/web" + "gopkg.in/h2non/filetype.v1" ) var fileBlacklist = map[string]bool{ @@ -218,15 +218,12 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { header = header[:n] // Determine the type of file from header - mimetype := mimemagic.Match("", header) - - // If the mime type is in our map, use that - // otherwise just use "ext" - if val, exists := mimeToExtension[mimetype]; exists { - extension = val - } else { + kind, err := filetype.Match(header) + if err != nil { extension = "ext" } + + extension = kind.Extension } upload.Filename = strings.Join([]string{barename, extension}, ".") From 37f9a0cbbc53a484ef7a415b1f813e5a5d1620f8 Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Sat, 25 Mar 2017 08:44:18 -0700 Subject: [PATCH 032/233] Change unknown extension from .ext to .file --- server_test.go | 12 ++++++------ upload.go | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/server_test.go b/server_test.go index a590465..6fe363c 100644 --- a/server_test.go +++ b/server_test.go @@ -699,7 +699,7 @@ func TestPutUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() - filename := generateBarename() + ".ext" + filename := generateBarename() + ".file" req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File content")) if err != nil { @@ -717,7 +717,7 @@ func TestPutRandomizedUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() - filename := generateBarename() + ".ext" + filename := generateBarename() + ".file" req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File content")) if err != nil { @@ -757,7 +757,7 @@ func TestPutEmptyUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() - filename := generateBarename() + ".ext" + filename := generateBarename() + ".file" req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("")) if err != nil { @@ -779,7 +779,7 @@ func TestPutJSONUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() - filename := generateBarename() + ".ext" + filename := generateBarename() + ".file" req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File content")) if err != nil { @@ -806,7 +806,7 @@ func TestPutRandomizedJSONUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() - filename := generateBarename() + ".ext" + filename := generateBarename() + ".file" req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File content")) if err != nil { @@ -834,7 +834,7 @@ func TestPutExpireJSONUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() - filename := generateBarename() + ".ext" + filename := generateBarename() + ".file" req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File content")) if err != nil { diff --git a/upload.go b/upload.go index bd2bed6..9cf8713 100644 --- a/upload.go +++ b/upload.go @@ -219,11 +219,11 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { // Determine the type of file from header kind, err := filetype.Match(header) - if err != nil { - extension = "ext" + if err != nil || kind.Extension == "unknown" { + extension = "file" + } else { + extension = kind.Extension } - - extension = kind.Extension } upload.Filename = strings.Join([]string{barename, extension}, ".") From bb7f7a177479896ab9ca8b15a4aee0009097c283 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sat, 25 Mar 2017 15:10:54 -0700 Subject: [PATCH 033/233] Remove mime type to extension map from util.go This is no longer used, so it can be removed. --- util.go | 770 -------------------------------------------------------- 1 file changed, 770 deletions(-) diff --git a/util.go b/util.go index c07a5f4..3074ebc 100644 --- a/util.go +++ b/util.go @@ -18,776 +18,6 @@ func supportedBinExtension(extension string) bool { return exists } -var mimeToExtension = map[string]string{ - "application/andrew-inset": "ez", - "application/applixware": "aw", - "application/atom+xml": "atom", - "application/atomcat+xml": "atomcat", - "application/atomsvc+xml": "atomsvc", - "application/ccxml+xml": "ccxml", - "application/cdmi-capability": "cdmia", - "application/cdmi-container": "cdmic", - "application/cdmi-domain": "cdmid", - "application/cdmi-object": "cdmio", - "application/cdmi-queue": "cdmiq", - "application/cu-seeme": "cu", - "application/davmount+xml": "davmount", - "application/docbook+xml": "dbk", - "application/dssc+der": "dssc", - "application/dssc+xml": "xdssc", - "application/ecmascript": "ecma", - "application/emma+xml": "emma", - "application/epub+zip": "epub", - "application/exi": "exi", - "application/font-tdpfr": "pfr", - "application/gml+xml": "gml", - "application/gpx+xml": "gpx", - "application/gxf": "gxf", - "application/hyperstudio": "stk", - "application/inkml+xml": "ink", - "application/ipfix": "ipfix", - "application/java-archive": "jar", - "application/java-serialized-object": "ser", - "application/java-vm": "class", - "application/javascript": "js", - "application/json": "json", - "application/jsonml+json": "jsonml", - "application/lost+xml": "lostxml", - "application/mac-binhex40": "hqx", - "application/mac-compactpro": "cpt", - "application/mads+xml": "mads", - "application/marc": "mrc", - "application/marcxml+xml": "mrcx", - "application/mathematica": "ma", - "application/mathml+xml": "mathml", - "application/mbox": "mbox", - "application/mediaservercontrol+xml": "mscml", - "application/metalink+xml": "metalink", - "application/metalink4+xml": "meta4", - "application/mets+xml": "mets", - "application/mods+xml": "mods", - "application/mp21": "mp21", - "application/mp4": "mp4s", - "application/msword": "doc", - "application/mxf": "mxf", - "application/octet-stream": "bin", - "application/oda": "oda", - "application/oebps-package+xml": "opf", - "application/ogg": "ogx", - "application/omdoc+xml": "omdoc", - "application/onenote": "onetoc", - "application/oxps": "oxps", - "application/patch-ops-error+xml": "xer", - "application/pdf": "pdf", - "application/pgp-encrypted": "pgp", - "application/pgp-signature": "asc", - "application/pics-rules": "prf", - "application/pkcs10": "p10", - "application/pkcs7-mime": "p7m", - "application/pkcs7-signature": "p7s", - "application/pkcs8": "p8", - "application/pkix-attr-cert": "ac", - "application/pkix-cert": "cer", - "application/pkix-crl": "crl", - "application/pkix-pkipath": "pkipath", - "application/pkixcmp": "pki", - "application/pls+xml": "pls", - "application/postscript": "ps", - "application/prs.cww": "cww", - "application/pskc+xml": "pskcxml", - "application/rdf+xml": "rdf", - "application/reginfo+xml": "rif", - "application/relax-ng-compact-syntax": "rnc", - "application/resource-lists+xml": "rl", - "application/resource-lists-diff+xml": "rld", - "application/rls-services+xml": "rs", - "application/rpki-ghostbusters": "gbr", - "application/rpki-manifest": "mft", - "application/rpki-roa": "roa", - "application/rsd+xml": "rsd", - "application/rss+xml": "rss", - "application/rtf": "rtf", - "application/sbml+xml": "sbml", - "application/scvp-cv-request": "scq", - "application/scvp-cv-response": "scs", - "application/scvp-vp-request": "spq", - "application/scvp-vp-response": "spp", - "application/sdp": "sdp", - "application/set-payment-initiation": "setpay", - "application/set-registration-initiation": "setreg", - "application/shf+xml": "shf", - "application/smil+xml": "smil", - "application/sparql-query": "rq", - "application/sparql-results+xml": "srx", - "application/srgs": "gram", - "application/srgs+xml": "grxml", - "application/sru+xml": "sru", - "application/ssdl+xml": "ssdl", - "application/ssml+xml": "ssml", - "application/tei+xml": "tei", - "application/thraud+xml": "tfi", - "application/timestamped-data": "tsd", - "application/vnd.3gpp.pic-bw-large": "plb", - "application/vnd.3gpp.pic-bw-small": "psb", - "application/vnd.3gpp.pic-bw-var": "pvb", - "application/vnd.3gpp2.tcap": "tcap", - "application/vnd.3m.post-it-notes": "pwn", - "application/vnd.accpac.simply.aso": "aso", - "application/vnd.accpac.simply.imp": "imp", - "application/vnd.acucobol": "acu", - "application/vnd.acucorp": "atc", - "application/vnd.adobe.air-application-installer-package+zip": "air", - "application/vnd.adobe.formscentral.fcdt": "fcdt", - "application/vnd.adobe.fxp": "fxp", - "application/vnd.adobe.xdp+xml": "xdp", - "application/vnd.adobe.xfdf": "xfdf", - "application/vnd.ahead.space": "ahead", - "application/vnd.airzip.filesecure.azf": "azf", - "application/vnd.airzip.filesecure.azs": "azs", - "application/vnd.amazon.ebook": "azw", - "application/vnd.americandynamics.acc": "acc", - "application/vnd.amiga.ami": "ami", - "application/vnd.android.package-archive": "apk", - "application/vnd.anser-web-certificate-issue-initiation": "cii", - "application/vnd.anser-web-funds-transfer-initiation": "fti", - "application/vnd.antix.game-component": "atx", - "application/vnd.apple.installer+xml": "mpkg", - "application/vnd.apple.mpegurl": "m3u8", - "application/vnd.aristanetworks.swi": "swi", - "application/vnd.astraea-software.iota": "iota", - "application/vnd.audiograph": "aep", - "application/vnd.blueice.multipass": "mpm", - "application/vnd.bmi": "bmi", - "application/vnd.businessobjects": "rep", - "application/vnd.chemdraw+xml": "cdxml", - "application/vnd.chipnuts.karaoke-mmd": "mmd", - "application/vnd.cinderella": "cdy", - "application/vnd.claymore": "cla", - "application/vnd.cloanto.rp9": "rp9", - "application/vnd.clonk.c4group": "c4g", - "application/vnd.cluetrust.cartomobile-config": "c11amc", - "application/vnd.cluetrust.cartomobile-config-pkg": "c11amz", - "application/vnd.commonspace": "csp", - "application/vnd.contact.cmsg": "cdbcmsg", - "application/vnd.cosmocaller": "cmc", - "application/vnd.crick.clicker": "clkx", - "application/vnd.crick.clicker.keyboard": "clkk", - "application/vnd.crick.clicker.palette": "clkp", - "application/vnd.crick.clicker.template": "clkt", - "application/vnd.crick.clicker.wordbank": "clkw", - "application/vnd.criticaltools.wbs+xml": "wbs", - "application/vnd.ctc-posml": "pml", - "application/vnd.cups-ppd": "ppd", - "application/vnd.curl.car": "car", - "application/vnd.curl.pcurl": "pcurl", - "application/vnd.dart": "dart", - "application/vnd.data-vision.rdz": "rdz", - "application/vnd.dece.data": "uvf", - "application/vnd.dece.ttml+xml": "uvt", - "application/vnd.dece.unspecified": "uvx", - "application/vnd.dece.zip": "uvz", - "application/vnd.denovo.fcselayout-link": "fe_launch", - "application/vnd.dna": "dna", - "application/vnd.dolby.mlp": "mlp", - "application/vnd.dpgraph": "dpg", - "application/vnd.dreamfactory": "dfac", - "application/vnd.ds-keypoint": "kpxx", - "application/vnd.dvb.ait": "ait", - "application/vnd.dvb.service": "svc", - "application/vnd.dynageo": "geo", - "application/vnd.ecowin.chart": "mag", - "application/vnd.enliven": "nml", - "application/vnd.epson.esf": "esf", - "application/vnd.epson.msf": "msf", - "application/vnd.epson.quickanime": "qam", - "application/vnd.epson.salt": "slt", - "application/vnd.epson.ssf": "ssf", - "application/vnd.eszigno3+xml": "es3", - "application/vnd.ezpix-album": "ez2", - "application/vnd.ezpix-package": "ez3", - "application/vnd.fdf": "fdf", - "application/vnd.fdsn.mseed": "mseed", - "application/vnd.fdsn.seed": "seed", - "application/vnd.flographit": "gph", - "application/vnd.fluxtime.clip": "ftc", - "application/vnd.framemaker": "fm", - "application/vnd.frogans.fnc": "fnc", - "application/vnd.frogans.ltf": "ltf", - "application/vnd.fsc.weblaunch": "fsc", - "application/vnd.fujitsu.oasys": "oas", - "application/vnd.fujitsu.oasys2": "oa2", - "application/vnd.fujitsu.oasys3": "oa3", - "application/vnd.fujitsu.oasysgp": "fg5", - "application/vnd.fujitsu.oasysprs": "bh2", - "application/vnd.fujixerox.ddd": "ddd", - "application/vnd.fujixerox.docuworks": "xdw", - "application/vnd.fujixerox.docuworks.binder": "xbd", - "application/vnd.fuzzysheet": "fzs", - "application/vnd.genomatix.tuxedo": "txd", - "application/vnd.geogebra.file": "ggb", - "application/vnd.geogebra.tool": "ggt", - "application/vnd.geometry-explorer": "gex", - "application/vnd.geonext": "gxt", - "application/vnd.geoplan": "g2w", - "application/vnd.geospace": "g3w", - "application/vnd.gmx": "gmx", - "application/vnd.google-earth.kml+xml": "kml", - "application/vnd.google-earth.kmz": "kmz", - "application/vnd.grafeq": "gqf", - "application/vnd.groove-account": "gac", - "application/vnd.groove-help": "ghf", - "application/vnd.groove-identity-message": "gim", - "application/vnd.groove-injector": "grv", - "application/vnd.groove-tool-message": "gtm", - "application/vnd.groove-tool-template": "tpl", - "application/vnd.groove-vcard": "vcg", - "application/vnd.hal+xml": "hal", - "application/vnd.handheld-entertainment+xml": "zmm", - "application/vnd.hbci": "hbci", - "application/vnd.hhe.lesson-player": "les", - "application/vnd.hp-hpgl": "hpgl", - "application/vnd.hp-hpid": "hpid", - "application/vnd.hp-hps": "hps", - "application/vnd.hp-jlyt": "jlt", - "application/vnd.hp-pcl": "pcl", - "application/vnd.hp-pclxl": "pclxl", - "application/vnd.hydrostatix.sof-data": "sfd-hdstx", - "application/vnd.ibm.minipay": "mpy", - "application/vnd.ibm.modcap": "afp", - "application/vnd.ibm.rights-management": "irm", - "application/vnd.ibm.secure-container": "sc", - "application/vnd.iccprofile": "icc", - "application/vnd.igloader": "igl", - "application/vnd.immervision-ivp": "ivp", - "application/vnd.immervision-ivu": "ivu", - "application/vnd.insors.igm": "igm", - "application/vnd.intercon.formnet": "xpw", - "application/vnd.intergeo": "i2g", - "application/vnd.intu.qbo": "qbo", - "application/vnd.intu.qfx": "qfx", - "application/vnd.ipunplugged.rcprofile": "rcprofile", - "application/vnd.irepository.package+xml": "irp", - "application/vnd.is-xpr": "xpr", - "application/vnd.isac.fcs": "fcs", - "application/vnd.jam": "jam", - "application/vnd.jcp.javame.midlet-rms": "rms", - "application/vnd.jisp": "jisp", - "application/vnd.joost.joda-archive": "joda", - "application/vnd.kahootz": "ktz", - "application/vnd.kde.karbon": "karbon", - "application/vnd.kde.kchart": "chrt", - "application/vnd.kde.kformula": "kfo", - "application/vnd.kde.kivio": "flw", - "application/vnd.kde.kontour": "kon", - "application/vnd.kde.kpresenter": "kpr", - "application/vnd.kde.kspread": "ksp", - "application/vnd.kde.kword": "kwd", - "application/vnd.kenameaapp": "htke", - "application/vnd.kidspiration": "kia", - "application/vnd.kinar": "kne", - "application/vnd.koan": "skp", - "application/vnd.kodak-descriptor": "sse", - "application/vnd.las.las+xml": "lasxml", - "application/vnd.llamagraphics.life-balance.desktop": "lbd", - "application/vnd.llamagraphics.life-balance.exchange+xml": "lbe", - "application/vnd.lotus-1-2-3": "123", - "application/vnd.lotus-approach": "apr", - "application/vnd.lotus-freelance": "pre", - "application/vnd.lotus-notes": "nsf", - "application/vnd.lotus-organizer": "org", - "application/vnd.lotus-screencam": "scm", - "application/vnd.lotus-wordpro": "lwp", - "application/vnd.macports.portpkg": "portpkg", - "application/vnd.mcd": "mcd", - "application/vnd.medcalcdata": "mc1", - "application/vnd.mediastation.cdkey": "cdkey", - "application/vnd.mfer": "mwf", - "application/vnd.mfmp": "mfm", - "application/vnd.micrografx.flo": "flo", - "application/vnd.micrografx.igx": "igx", - "application/vnd.mif": "mif", - "application/vnd.mobius.daf": "daf", - "application/vnd.mobius.dis": "dis", - "application/vnd.mobius.mbk": "mbk", - "application/vnd.mobius.mqy": "mqy", - "application/vnd.mobius.msl": "msl", - "application/vnd.mobius.plc": "plc", - "application/vnd.mobius.txf": "txf", - "application/vnd.mophun.application": "mpn", - "application/vnd.mophun.certificate": "mpc", - "application/vnd.mozilla.xul+xml": "xul", - "application/vnd.ms-artgalry": "cil", - "application/vnd.ms-cab-compressed": "cab", - "application/vnd.ms-excel": "xls", - "application/vnd.ms-excel.addin.macroenabled.12": "xlam", - "application/vnd.ms-excel.sheet.binary.macroenabled.12": "xlsb", - "application/vnd.ms-excel.sheet.macroenabled.12": "xlsm", - "application/vnd.ms-excel.template.macroenabled.12": "xltm", - "application/vnd.ms-fontobject": "eot", - "application/vnd.ms-htmlhelp": "chm", - "application/vnd.ms-ims": "ims", - "application/vnd.ms-lrm": "lrm", - "application/vnd.ms-officetheme": "thmx", - "application/vnd.ms-pki.seccat": "cat", - "application/vnd.ms-pki.stl": "stl", - "application/vnd.ms-powerpoint": "ppt", - "application/vnd.ms-powerpoint.addin.macroenabled.12": "ppam", - "application/vnd.ms-powerpoint.presentation.macroenabled.12": "pptm", - "application/vnd.ms-powerpoint.slide.macroenabled.12": "sldm", - "application/vnd.ms-powerpoint.slideshow.macroenabled.12": "ppsm", - "application/vnd.ms-powerpoint.template.macroenabled.12": "potm", - "application/vnd.ms-project": "mpp", - "application/vnd.ms-word.document.macroenabled.12": "docm", - "application/vnd.ms-word.template.macroenabled.12": "dotm", - "application/vnd.ms-works": "wps", - "application/vnd.ms-wpl": "wpl", - "application/vnd.ms-xpsdocument": "xps", - "application/vnd.mseq": "mseq", - "application/vnd.musician": "mus", - "application/vnd.muvee.style": "msty", - "application/vnd.mynfc": "taglet", - "application/vnd.neurolanguage.nlu": "nlu", - "application/vnd.nitf": "ntf", - "application/vnd.noblenet-directory": "nnd", - "application/vnd.noblenet-sealer": "nns", - "application/vnd.noblenet-web": "nnw", - "application/vnd.nokia.n-gage.data": "ngdat", - "application/vnd.nokia.n-gage.symbian.install": "n-gage", - "application/vnd.nokia.radio-preset": "rpst", - "application/vnd.nokia.radio-presets": "rpss", - "application/vnd.novadigm.edm": "edm", - "application/vnd.novadigm.edx": "edx", - "application/vnd.novadigm.ext": "ext", - "application/vnd.oasis.opendocument.chart": "odc", - "application/vnd.oasis.opendocument.chart-template": "otc", - "application/vnd.oasis.opendocument.database": "odb", - "application/vnd.oasis.opendocument.formula": "odf", - "application/vnd.oasis.opendocument.formula-template": "odft", - "application/vnd.oasis.opendocument.graphics": "odg", - "application/vnd.oasis.opendocument.graphics-template": "otg", - "application/vnd.oasis.opendocument.image": "odi", - "application/vnd.oasis.opendocument.image-template": "oti", - "application/vnd.oasis.opendocument.presentation": "odp", - "application/vnd.oasis.opendocument.presentation-template": "otp", - "application/vnd.oasis.opendocument.spreadsheet": "ods", - "application/vnd.oasis.opendocument.spreadsheet-template": "ots", - "application/vnd.oasis.opendocument.text": "odt", - "application/vnd.oasis.opendocument.text-master": "odm", - "application/vnd.oasis.opendocument.text-template": "ott", - "application/vnd.oasis.opendocument.text-web": "oth", - "application/vnd.olpc-sugar": "xo", - "application/vnd.oma.dd2+xml": "dd2", - "application/vnd.openofficeorg.extension": "oxt", - "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx", - "application/vnd.openxmlformats-officedocument.presentationml.slide": "sldx", - "application/vnd.openxmlformats-officedocument.presentationml.slideshow": "ppsx", - "application/vnd.openxmlformats-officedocument.presentationml.template": "potx", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx", - "application/vnd.openxmlformats-officedocument.spreadsheetml.template": "xltx", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx", - "application/vnd.openxmlformats-officedocument.wordprocessingml.template": "dotx", - "application/vnd.osgeo.mapguide.package": "mgp", - "application/vnd.osgi.dp": "dp", - "application/vnd.osgi.subsystem": "esa", - "application/vnd.palm": "pdb", - "application/vnd.pawaafile": "paw", - "application/vnd.pg.format": "str", - "application/vnd.pg.osasli": "ei6", - "application/vnd.picsel": "efif", - "application/vnd.pmi.widget": "wg", - "application/vnd.pocketlearn": "plf", - "application/vnd.powerbuilder6": "pbd", - "application/vnd.previewsystems.box": "box", - "application/vnd.proteus.magazine": "mgz", - "application/vnd.publishare-delta-tree": "qps", - "application/vnd.pvi.ptid1": "ptid", - "application/vnd.quark.quarkxpress": "qxd", - "application/vnd.realvnc.bed": "bed", - "application/vnd.recordare.musicxml": "mxl", - "application/vnd.recordare.musicxml+xml": "musicxml", - "application/vnd.rig.cryptonote": "cryptonote", - "application/vnd.rim.cod": "cod", - "application/vnd.rn-realmedia": "rm", - "application/vnd.rn-realmedia-vbr": "rmvb", - "application/vnd.route66.link66+xml": "link66", - "application/vnd.sailingtracker.track": "st", - "application/vnd.seemail": "see", - "application/vnd.sema": "sema", - "application/vnd.semd": "semd", - "application/vnd.semf": "semf", - "application/vnd.shana.informed.formdata": "ifm", - "application/vnd.shana.informed.formtemplate": "itp", - "application/vnd.shana.informed.interchange": "iif", - "application/vnd.shana.informed.package": "ipk", - "application/vnd.simtech-mindmapper": "twd", - "application/vnd.smaf": "mmf", - "application/vnd.smart.teacher": "teacher", - "application/vnd.solent.sdkm+xml": "sdkm", - "application/vnd.spotfire.dxp": "dxp", - "application/vnd.spotfire.sfs": "sfs", - "application/vnd.stardivision.calc": "sdc", - "application/vnd.stardivision.draw": "sda", - "application/vnd.stardivision.impress": "sdd", - "application/vnd.stardivision.math": "smf", - "application/vnd.stardivision.writer": "sdw", - "application/vnd.stardivision.writer-global": "sgl", - "application/vnd.stepmania.package": "smzip", - "application/vnd.stepmania.stepchart": "sm", - "application/vnd.sun.xml.calc": "sxc", - "application/vnd.sun.xml.calc.template": "stc", - "application/vnd.sun.xml.draw": "sxd", - "application/vnd.sun.xml.draw.template": "std", - "application/vnd.sun.xml.impress": "sxi", - "application/vnd.sun.xml.impress.template": "sti", - "application/vnd.sun.xml.math": "sxm", - "application/vnd.sun.xml.writer": "sxw", - "application/vnd.sun.xml.writer.global": "sxg", - "application/vnd.sun.xml.writer.template": "stw", - "application/vnd.sus-calendar": "sus", - "application/vnd.svd": "svd", - "application/vnd.symbian.install": "sis", - "application/vnd.syncml+xml": "xsm", - "application/vnd.syncml.dm+wbxml": "bdm", - "application/vnd.syncml.dm+xml": "xdm", - "application/vnd.tao.intent-module-archive": "tao", - "application/vnd.tcpdump.pcap": "pcap", - "application/vnd.tmobile-livetv": "tmo", - "application/vnd.trid.tpt": "tpt", - "application/vnd.triscape.mxs": "mxs", - "application/vnd.trueapp": "tra", - "application/vnd.ufdl": "ufdl", - "application/vnd.uiq.theme": "utz", - "application/vnd.umajin": "umj", - "application/vnd.unity": "unityweb", - "application/vnd.uoml+xml": "uoml", - "application/vnd.vcx": "vcx", - "application/vnd.visio": "vsd", - "application/vnd.visionary": "vis", - "application/vnd.vsf": "vsf", - "application/vnd.wap.wbxml": "wbxml", - "application/vnd.wap.wmlc": "wmlc", - "application/vnd.wap.wmlscriptc": "wmlsc", - "application/vnd.webturbo": "wtb", - "application/vnd.wolfram.player": "nbp", - "application/vnd.wordperfect": "wpd", - "application/vnd.wqd": "wqd", - "application/vnd.wt.stf": "stf", - "application/vnd.xara": "xar", - "application/vnd.xfdl": "xfdl", - "application/vnd.yamaha.hv-dic": "hvd", - "application/vnd.yamaha.hv-script": "hvs", - "application/vnd.yamaha.hv-voice": "hvp", - "application/vnd.yamaha.openscoreformat": "osf", - "application/vnd.yamaha.openscoreformat.osfpvg+xml": "osfpvg", - "application/vnd.yamaha.smaf-audio": "saf", - "application/vnd.yamaha.smaf-phrase": "spf", - "application/vnd.yellowriver-custom-menu": "cmp", - "application/vnd.zul": "zir", - "application/vnd.zzazz.deck+xml": "zaz", - "application/voicexml+xml": "vxml", - "application/widget": "wgt", - "application/winhlp": "hlp", - "application/wsdl+xml": "wsdl", - "application/wspolicy+xml": "wspolicy", - "application/x-7z-compressed": "7z", - "application/x-abiword": "abw", - "application/x-ace-compressed": "ace", - "application/x-apple-diskimage": "dmg", - "application/x-authorware-bin": "aab", - "application/x-authorware-map": "aam", - "application/x-authorware-seg": "aas", - "application/x-bcpio": "bcpio", - "application/x-bittorrent": "torrent", - "application/x-blorb": "blb", - "application/x-bzip": "bz", - "application/x-bzip2": "bz2", - "application/x-cbr": "cbr", - "application/x-cdlink": "vcd", - "application/x-cfs-compressed": "cfs", - "application/x-chat": "chat", - "application/x-chess-pgn": "pgn", - "application/x-conference": "nsc", - "application/x-cpio": "cpio", - "application/x-csh": "csh", - "application/x-debian-package": "deb", - "application/x-dgc-compressed": "dgc", - "application/x-director": "dir", - "application/x-doom": "wad", - "application/x-dtbncx+xml": "ncx", - "application/x-dtbook+xml": "dtb", - "application/x-dtbresource+xml": "res", - "application/x-dvi": "dvi", - "application/x-envoy": "evy", - "application/x-eva": "eva", - "application/x-font-bdf": "bdf", - "application/x-font-ghostscript": "gsf", - "application/x-font-linux-psf": "psf", - "application/x-font-otf": "otf", - "application/x-font-pcf": "pcf", - "application/x-font-snf": "snf", - "application/x-font-ttf": "ttf", - "application/x-font-type1": "pfa", - "application/x-font-woff": "woff", - "application/x-freearc": "arc", - "application/x-futuresplash": "spl", - "application/x-gca-compressed": "gca", - "application/x-glulx": "ulx", - "application/x-gnumeric": "gnumeric", - "application/x-gramps-xml": "gramps", - "application/x-gtar": "gtar", - "application/x-hdf": "hdf", - "application/x-install-instructions": "install", - "application/x-iso9660-image": "iso", - "application/x-java-jnlp-file": "jnlp", - "application/x-latex": "latex", - "application/x-lzh-compressed": "lzh", - "application/x-mie": "mie", - "application/x-mobipocket-ebook": "prc", - "application/x-ms-application": "application", - "application/x-ms-shortcut": "lnk", - "application/x-ms-wmd": "wmd", - "application/x-ms-wmz": "wmz", - "application/x-ms-xbap": "xbap", - "application/x-msaccess": "mdb", - "application/x-msbinder": "obd", - "application/x-mscardfile": "crd", - "application/x-msclip": "clp", - "application/x-msdownload": "exe", - "application/x-msmediaview": "mvb", - "application/x-msmetafile": "wmf", - "application/x-msmoney": "mny", - "application/x-mspublisher": "pub", - "application/x-msschedule": "scd", - "application/x-msterminal": "trm", - "application/x-mswrite": "wri", - "application/x-netcdf": "nc", - "application/x-nzb": "nzb", - "application/x-pkcs12": "p12", - "application/x-pkcs7-certificates": "p7b", - "application/x-pkcs7-certreqresp": "p7r", - "application/x-rar-compressed": "rar", - "application/x-research-info-systems": "ris", - "application/x-sh": "sh", - "application/x-shar": "shar", - "application/x-shockwave-flash": "swf", - "application/x-silverlight-app": "xap", - "application/x-sql": "sql", - "application/x-stuffit": "sit", - "application/x-stuffitx": "sitx", - "application/x-subrip": "srt", - "application/x-sv4cpio": "sv4cpio", - "application/x-sv4crc": "sv4crc", - "application/x-t3vm-image": "t3", - "application/x-tads": "gam", - "application/x-tar": "tar", - "application/x-tcl": "tcl", - "application/x-tex": "tex", - "application/x-tex-tfm": "tfm", - "application/x-texinfo": "texinfo", - "application/x-tgif": "obj", - "application/x-ustar": "ustar", - "application/x-wais-source": "src", - "application/x-x509-ca-cert": "der", - "application/x-xfig": "fig", - "application/x-xliff+xml": "xlf", - "application/x-xpinstall": "xpi", - "application/x-xz": "xz", - "application/x-zmachine": "z1", - "application/xaml+xml": "xaml", - "application/xcap-diff+xml": "xdf", - "application/xenc+xml": "xenc", - "application/xhtml+xml": "xhtml", - "application/xml": "xml", - "application/xml-dtd": "dtd", - "application/xop+xml": "xop", - "application/xproc+xml": "xpl", - "application/xslt+xml": "xslt", - "application/xspf+xml": "xspf", - "application/xv+xml": "mxml", - "application/yang": "yang", - "application/yin+xml": "yin", - "application/zip": "zip", - "audio/adpcm": "adp", - "audio/basic": "au", - "audio/midi": "mid", - "audio/mp4": "mp4a", - "audio/mpeg": "mpga", - "audio/ogg": "oga", - "audio/s3m": "s3m", - "audio/silk": "sil", - "audio/vnd.dece.audio": "uva", - "audio/vnd.digital-winds": "eol", - "audio/vnd.dra": "dra", - "audio/vnd.dts": "dts", - "audio/vnd.dts.hd": "dtshd", - "audio/vnd.lucent.voice": "lvp", - "audio/vnd.ms-playready.media.pya": "pya", - "audio/vnd.nuera.ecelp4800": "ecelp4800", - "audio/vnd.nuera.ecelp7470": "ecelp7470", - "audio/vnd.nuera.ecelp9600": "ecelp9600", - "audio/vnd.rip": "rip", - "audio/webm": "weba", - "audio/x-aac": "aac", - "audio/x-aiff": "aif", - "audio/x-caf": "caf", - "audio/x-flac": "flac", - "audio/x-matroska": "mka", - "audio/x-mpegurl": "m3u", - "audio/x-ms-wax": "wax", - "audio/x-ms-wma": "wma", - "audio/x-pn-realaudio": "ram", - "audio/x-pn-realaudio-plugin": "rmp", - "audio/x-wav": "wav", - "audio/xm": "xm", - "chemical/x-cdx": "cdx", - "chemical/x-cif": "cif", - "chemical/x-cmdf": "cmdf", - "chemical/x-cml": "cml", - "chemical/x-csml": "csml", - "chemical/x-xyz": "xyz", - "image/bmp": "bmp", - "image/cgm": "cgm", - "image/g3fax": "g3", - "image/gif": "gif", - "image/ief": "ief", - "image/jpeg": "jpg", - "image/ktx": "ktx", - "image/png": "png", - "image/prs.btif": "btif", - "image/sgi": "sgi", - "image/svg+xml": "svg", - "image/tiff": "tiff", - "image/vnd.adobe.photoshop": "psd", - "image/vnd.dece.graphic": "uvi", - "image/vnd.dvb.subtitle": "sub", - "image/vnd.djvu": "djvu", - "image/vnd.dwg": "dwg", - "image/vnd.dxf": "dxf", - "image/vnd.fastbidsheet": "fbs", - "image/vnd.fpx": "fpx", - "image/vnd.fst": "fst", - "image/vnd.fujixerox.edmics-mmr": "mmr", - "image/vnd.fujixerox.edmics-rlc": "rlc", - "image/vnd.ms-modi": "mdi", - "image/vnd.ms-photo": "wdp", - "image/vnd.net-fpx": "npx", - "image/vnd.wap.wbmp": "wbmp", - "image/vnd.xiff": "xif", - "image/webp": "webp", - "image/x-3ds": "3ds", - "image/x-cmu-raster": "ras", - "image/x-cmx": "cmx", - "image/x-freehand": "fh", - "image/x-icon": "ico", - "image/x-mrsid-image": "sid", - "image/x-pcx": "pcx", - "image/x-pict": "pic", - "image/x-portable-anymap": "pnm", - "image/x-portable-bitmap": "pbm", - "image/x-portable-graymap": "pgm", - "image/x-portable-pixmap": "ppm", - "image/x-rgb": "rgb", - "image/x-tga": "tga", - "image/x-xbitmap": "xbm", - "image/x-xpixmap": "xpm", - "image/x-xwindowdump": "xwd", - "message/rfc822": "eml", - "model/iges": "igs", - "model/mesh": "mesh", - "model/vnd.collada+xml": "dae", - "model/vnd.dwf": "dwf", - "model/vnd.gdl": "gdl", - "model/vnd.gtw": "gtw", - "model/vnd.mts": "mts", - "model/vnd.vtu": "vtu", - "model/vrml": "wrl", - "model/x3d+binary": "x3db", - "model/x3d+vrml": "x3dv", - "model/x3d+xml": "x3d", - "text/cache-manifest": "appcache", - "text/calendar": "ics", - "text/css": "css", - "text/csv": "csv", - "text/html": "html", - "text/n3": "n3", - "text/plain": "txt", - "text/prs.lines.tag": "dsc", - "text/richtext": "rtx", - "text/sgml": "sgml", - "text/tab-separated-values": "tsv", - "text/troff": "t", - "text/turtle": "ttl", - "text/uri-list": "uri", - "text/vcard": "vcard", - "text/vnd.curl": "curl", - "text/vnd.curl.dcurl": "dcurl", - "text/vnd.curl.scurl": "scurl", - "text/vnd.curl.mcurl": "mcurl", - "text/vnd.dvb.subtitle": "sub", - "text/vnd.fly": "fly", - "text/vnd.fmi.flexstor": "flx", - "text/vnd.graphviz": "gv", - "text/vnd.in3d.3dml": "3dml", - "text/vnd.in3d.spot": "spot", - "text/vnd.sun.j2me.app-descriptor": "jad", - "text/vnd.wap.wml": "wml", - "text/vnd.wap.wmlscript": "wmls", - "text/x-asm": "s", - "text/x-c": "c", - "text/x-fortran": "f", - "text/x-java-source": "java", - "text/x-opml": "opml", - "text/x-pascal": "p", - "text/x-nfo": "nfo", - "text/x-shellscript": "sh", - "text/x-setext": "etx", - "text/x-sfv": "sfv", - "text/x-tex": "tex", - "text/x-uuencode": "uu", - "text/x-vcalendar": "vcs", - "text/x-vcard": "vcf", - "video/3gpp": "3gp", - "video/3gpp2": "3g2", - "video/h261": "h261", - "video/h263": "h263", - "video/h264": "h264", - "video/jpeg": "jpgv", - "video/jpm": "jpm", - "video/mj2": "mj2", - "video/mp4": "mp4", - "video/mpeg": "mpeg", - "video/ogg": "ogv", - "video/quicktime": "qt", - "video/vnd.dece.hd": "uvh", - "video/vnd.dece.mobile": "uvm", - "video/vnd.dece.pd": "uvp", - "video/vnd.dece.sd": "uvs", - "video/vnd.dece.video": "uvv", - "video/vnd.dvb.file": "dvb", - "video/vnd.fvt": "fvt", - "video/vnd.mpegurl": "m4u", - "video/vnd.ms-playready.media.pyv": "pyv", - "video/vnd.uvvu.mp4": "uvvu", - "video/vnd.vivo": "viv", - "video/webm": "webm", - "video/x-f4v": "f4v", - "video/x-fli": "fli", - "video/x-flv": "flv", - "video/x-m4v": "m4v", - "video/x-matroska": "mkv", - "video/x-mng": "mng", - "video/x-ms-asf": "asf", - "video/x-ms-vob": "vob", - "video/x-ms-wm": "wm", - "video/x-ms-wmv": "wmv", - "video/x-ms-wmx": "wmx", - "video/x-ms-wvx": "wvx", - "video/x-msvideo": "avi", - "video/x-sgi-movie": "movie", - "video/x-smv": "smv", - "x-conference/x-cooltalk": "ice", -} - var extensionToAce = map[string]string{ "c": "c_cpp", "h": "c_cpp", From b7fadd96765fdcc45e023c6e4c60280b1b5906d5 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Mon, 1 May 2017 21:25:56 -0700 Subject: [PATCH 034/233] Add linx-cleanup tool This doesn't completely fix #116, but it makes setting up a cron job to do cleanup much more pleasant. --- .gitignore | 1 + backends/localfs/localfs.go | 15 ++++++ backends/meta.go | 23 +++++++++ backends/metajson/metajson.go | 73 +++++++++++++++++++++++++++ backends/{backends.go => storage.go} | 5 ++ delete.go | 2 +- display.go | 3 +- expiry.go | 23 +++------ expiry/expiry.go | 13 +++++ fileserve.go | 5 +- linx-cleanup/cleanup.go | 45 +++++++++++++++++ meta.go | 75 ++++------------------------ server.go | 7 ++- torrent.go | 3 +- upload.go | 20 ++++---- 15 files changed, 216 insertions(+), 97 deletions(-) create mode 100644 backends/meta.go create mode 100644 backends/metajson/metajson.go rename backends/{backends.go => storage.go} (84%) create mode 100644 expiry/expiry.go create mode 100644 linx-cleanup/cleanup.go diff --git a/.gitignore b/.gitignore index 79de414..bd9da44 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ _testmain.go linx-server files/ meta/ +linx-cleanup diff --git a/backends/localfs/localfs.go b/backends/localfs/localfs.go index 148cf2e..b55c986 100644 --- a/backends/localfs/localfs.go +++ b/backends/localfs/localfs.go @@ -65,6 +65,21 @@ func (b LocalfsBackend) Size(key string) (int64, error) { return fileInfo.Size(), nil } +func (b LocalfsBackend) List() ([]string, error) { + var output []string + + files, err := ioutil.ReadDir(b.basePath) + if err != nil { + return nil, err + } + + for _, file := range files { + output = append(output, file.Name()) + } + + return output, nil +} + func NewLocalfsBackend(basePath string) LocalfsBackend { return LocalfsBackend{basePath: basePath} } diff --git a/backends/meta.go b/backends/meta.go new file mode 100644 index 0000000..eb17d5e --- /dev/null +++ b/backends/meta.go @@ -0,0 +1,23 @@ +package backends + +import ( + "errors" + "time" +) + +type MetaBackend interface { + Get(key string) (Metadata, error) + Put(key string, metadata *Metadata) error +} + +type Metadata struct { + DeleteKey string + Sha256sum string + Mimetype string + Size int64 + Expiry time.Time + ArchiveFiles []string + ShortURL string +} + +var BadMetadata = errors.New("Corrupted metadata.") diff --git a/backends/metajson/metajson.go b/backends/metajson/metajson.go new file mode 100644 index 0000000..6e76dd4 --- /dev/null +++ b/backends/metajson/metajson.go @@ -0,0 +1,73 @@ +package metajson + +import ( + "bytes" + "encoding/json" + "time" + + "github.com/andreimarcu/linx-server/backends" +) + +type MetadataJSON struct { + DeleteKey string `json:"delete_key"` + Sha256sum string `json:"sha256sum"` + Mimetype string `json:"mimetype"` + Size int64 `json:"size"` + Expiry int64 `json:"expiry"` + ArchiveFiles []string `json:"archive_files,omitempty"` + ShortURL string `json:"short_url"` +} + +type MetaJSONBackend struct { + storage backends.MetaStorageBackend +} + +func (m MetaJSONBackend) Put(key string, metadata *backends.Metadata) error { + mjson := MetadataJSON{} + mjson.DeleteKey = metadata.DeleteKey + mjson.Mimetype = metadata.Mimetype + mjson.ArchiveFiles = metadata.ArchiveFiles + mjson.Sha256sum = metadata.Sha256sum + mjson.Expiry = metadata.Expiry.Unix() + mjson.Size = metadata.Size + mjson.ShortURL = metadata.ShortURL + + byt, err := json.Marshal(mjson) + if err != nil { + return err + } + + if _, err := m.storage.Put(key, bytes.NewBuffer(byt)); err != nil { + return err + } + + return nil +} + +func (m MetaJSONBackend) Get(key string) (metadata backends.Metadata, err error) { + b, err := m.storage.Get(key) + if err != nil { + return metadata, backends.BadMetadata + } + + mjson := MetadataJSON{} + + err = json.Unmarshal(b, &mjson) + if err != nil { + return metadata, backends.BadMetadata + } + + metadata.DeleteKey = mjson.DeleteKey + metadata.Mimetype = mjson.Mimetype + metadata.ArchiveFiles = mjson.ArchiveFiles + metadata.Sha256sum = mjson.Sha256sum + metadata.Expiry = time.Unix(mjson.Expiry, 0) + metadata.Size = mjson.Size + metadata.ShortURL = mjson.ShortURL + + return +} + +func NewMetaJSONBackend(storage backends.MetaStorageBackend) MetaJSONBackend { + return MetaJSONBackend{storage: storage} +} diff --git a/backends/backends.go b/backends/storage.go similarity index 84% rename from backends/backends.go rename to backends/storage.go index 42a33f0..2b51a2c 100644 --- a/backends/backends.go +++ b/backends/storage.go @@ -21,3 +21,8 @@ type StorageBackend interface { ServeFile(key string, w http.ResponseWriter, r *http.Request) Size(key string) (int64, error) } + +type MetaStorageBackend interface { + StorageBackend + List() ([]string, error) +} diff --git a/delete.go b/delete.go index e42e623..61c6fa8 100644 --- a/delete.go +++ b/delete.go @@ -28,7 +28,7 @@ func deleteHandler(c web.C, w http.ResponseWriter, r *http.Request) { if metadata.DeleteKey == requestKey { fileDelErr := fileBackend.Delete(filename) - metaDelErr := metaBackend.Delete(filename) + metaDelErr := metaStorageBackend.Delete(filename) if (fileDelErr != nil) || (metaDelErr != nil) { oopsHandler(c, w, r, RespPLAIN, "Could not delete") diff --git a/display.go b/display.go index b196477..c6d8470 100644 --- a/display.go +++ b/display.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/andreimarcu/linx-server/expiry" "github.com/dustin/go-humanize" "github.com/flosch/pongo2" "github.com/microcosm-cc/bluemonday" @@ -32,7 +33,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } var expiryHuman string - if metadata.Expiry != neverExpire { + if metadata.Expiry != expiry.NeverExpire { expiryHuman = humanize.RelTime(time.Now(), metadata.Expiry, "", "") } sizeHuman := humanize.Bytes(uint64(metadata.Size)) diff --git a/expiry.go b/expiry.go index 4172f51..6d8887d 100644 --- a/expiry.go +++ b/expiry.go @@ -3,6 +3,7 @@ package main import ( "time" + "github.com/andreimarcu/linx-server/expiry" "github.com/dustin/go-humanize" ) @@ -21,14 +22,6 @@ type ExpirationTime struct { Human string } -var neverExpire = time.Unix(0, 0) - -// Determine if a file with expiry set to "ts" has expired yet -func isTsExpired(ts time.Time) bool { - now := time.Now() - return ts != neverExpire && now.After(ts) -} - // Determine if the given filename is expired func isFileExpired(filename string) (bool, error) { metadata, err := metadataRead(filename) @@ -36,7 +29,7 @@ func isFileExpired(filename string) (bool, error) { return false, err } - return isTsExpired(metadata.Expiry), nil + return expiry.IsTsExpired(metadata.Expiry), nil } // Return a list of expiration times and their humanized versions @@ -45,16 +38,16 @@ func listExpirationTimes() []ExpirationTime { actualExpiryInList := false var expiryList []ExpirationTime - for _, expiry := range defaultExpiryList { - if Config.maxExpiry == 0 || expiry <= Config.maxExpiry { - if expiry == Config.maxExpiry { + for _, expiryEntry := range defaultExpiryList { + if Config.maxExpiry == 0 || expiryEntry <= Config.maxExpiry { + if expiryEntry == Config.maxExpiry { actualExpiryInList = true } - duration := time.Duration(expiry) * time.Second + duration := time.Duration(expiryEntry) * time.Second expiryList = append(expiryList, ExpirationTime{ - expiry, - humanize.RelTime(epoch, epoch.Add(duration), "", ""), + Seconds: expiryEntry, + Human: humanize.RelTime(epoch, epoch.Add(duration), "", ""), }) } } diff --git a/expiry/expiry.go b/expiry/expiry.go new file mode 100644 index 0000000..a1a4e54 --- /dev/null +++ b/expiry/expiry.go @@ -0,0 +1,13 @@ +package expiry + +import ( + "time" +) + +var NeverExpire = time.Unix(0, 0) + +// Determine if a file with expiry set to "ts" has expired yet +func IsTsExpired(ts time.Time) bool { + now := time.Now() + return ts != NeverExpire && now.After(ts) +} diff --git a/fileserve.go b/fileserve.go index 1a48a74..f7f87a6 100644 --- a/fileserve.go +++ b/fileserve.go @@ -5,6 +5,7 @@ import ( "net/url" "strings" + "github.com/andreimarcu/linx-server/backends" "github.com/zenazn/goji/web" ) @@ -15,7 +16,7 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { if err == NotFoundErr { notFoundHandler(c, w, r) return - } else if err == BadMetadata { + } else if err == backends.BadMetadata { oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") return } @@ -72,7 +73,7 @@ func checkFile(filename string) error { if expired { fileBackend.Delete(filename) - metaBackend.Delete(filename) + metaStorageBackend.Delete(filename) return NotFoundErr } diff --git a/linx-cleanup/cleanup.go b/linx-cleanup/cleanup.go new file mode 100644 index 0000000..7607fe6 --- /dev/null +++ b/linx-cleanup/cleanup.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "log" + + "github.com/andreimarcu/linx-server/backends/localfs" + "github.com/andreimarcu/linx-server/backends/metajson" + "github.com/andreimarcu/linx-server/expiry" +) + +func main() { + var filesDir string + var metaDir string + var noLogs bool + + flag.StringVar(&filesDir, "filespath", "files/", + "path to files directory") + flag.StringVar(&metaDir, "metapath", "meta/", + "path to metadata directory") + flag.BoolVar(&noLogs, "nologs", false, + "don't log deleted files") + + metaStorageBackend := localfs.NewLocalfsBackend(metaDir) + metaBackend := metajson.NewMetaJSONBackend(metaStorageBackend) + fileBackend := localfs.NewLocalfsBackend(filesDir) + + files, err := metaStorageBackend.List() + if err != nil { + panic(err) + } + + for _, filename := range files { + metadata, err := metaBackend.Get(filename) + if err != nil { + log.Printf("Failed to find metadata for %s", filename) + } + + if expiry.IsTsExpired(metadata.Expiry) { + log.Printf("Delete %s", filename) + fileBackend.Delete(filename) + metaStorageBackend.Delete(filename) + } + } +} diff --git a/meta.go b/meta.go index d2fb637..10cd4e1 100644 --- a/meta.go +++ b/meta.go @@ -3,46 +3,25 @@ package main import ( "archive/tar" "archive/zip" - "bytes" "compress/bzip2" "compress/gzip" "crypto/sha256" "encoding/hex" - "encoding/json" "errors" "io" "sort" "time" "unicode" + "github.com/andreimarcu/linx-server/backends" + "github.com/andreimarcu/linx-server/expiry" "github.com/dchest/uniuri" "gopkg.in/h2non/filetype.v1" ) -type MetadataJSON struct { - DeleteKey string `json:"delete_key"` - Sha256sum string `json:"sha256sum"` - Mimetype string `json:"mimetype"` - Size int64 `json:"size"` - Expiry int64 `json:"expiry"` - ArchiveFiles []string `json:"archive_files,omitempty"` - ShortURL string `json:"short_url"` -} - -type Metadata struct { - DeleteKey string - Sha256sum string - Mimetype string - Size int64 - Expiry time.Time - ArchiveFiles []string - ShortURL string -} - var NotFoundErr = errors.New("File not found.") -var BadMetadata = errors.New("Corrupted metadata.") -func generateMetadata(fName string, exp time.Time, delKey string) (m Metadata, err error) { +func generateMetadata(fName string, exp time.Time, delKey string) (m backends.Metadata, err error) { file, err := fileBackend.Open(fName) if err != nil { return @@ -145,59 +124,23 @@ func generateMetadata(fName string, exp time.Time, delKey string) (m Metadata, e return } -func metadataWrite(filename string, metadata *Metadata) error { - mjson := MetadataJSON{} - mjson.DeleteKey = metadata.DeleteKey - mjson.Mimetype = metadata.Mimetype - mjson.ArchiveFiles = metadata.ArchiveFiles - mjson.Sha256sum = metadata.Sha256sum - mjson.Expiry = metadata.Expiry.Unix() - mjson.Size = metadata.Size - mjson.ShortURL = metadata.ShortURL - - byt, err := json.Marshal(mjson) - if err != nil { - return err - } - - if _, err := metaBackend.Put(filename, bytes.NewBuffer(byt)); err != nil { - return err - } - - return nil +func metadataWrite(filename string, metadata *backends.Metadata) error { + return metaBackend.Put(filename, metadata) } -func metadataRead(filename string) (metadata Metadata, err error) { - b, err := metaBackend.Get(filename) +func metadataRead(filename string) (metadata backends.Metadata, err error) { + metadata, err = metaBackend.Get(filename) if err != nil { // Metadata does not exist, generate one - newMData, err := generateMetadata(filename, neverExpire, "") + newMData, err := generateMetadata(filename, expiry.NeverExpire, "") if err != nil { return metadata, err } metadataWrite(filename, &newMData) - b, err = metaBackend.Get(filename) - if err != nil { - return metadata, BadMetadata - } + metadata, err = metaBackend.Get(filename) } - mjson := MetadataJSON{} - - err = json.Unmarshal(b, &mjson) - if err != nil { - return metadata, BadMetadata - } - - metadata.DeleteKey = mjson.DeleteKey - metadata.Mimetype = mjson.Mimetype - metadata.ArchiveFiles = mjson.ArchiveFiles - metadata.Sha256sum = mjson.Sha256sum - metadata.Expiry = time.Unix(mjson.Expiry, 0) - metadata.Size = mjson.Size - metadata.ShortURL = mjson.ShortURL - return } diff --git a/server.go b/server.go index ac229fe..daaf89e 100644 --- a/server.go +++ b/server.go @@ -16,6 +16,7 @@ import ( "github.com/GeertJohan/go.rice" "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/backends/localfs" + "github.com/andreimarcu/linx-server/backends/metajson" "github.com/flosch/pongo2" "github.com/vharitonsky/iniflags" "github.com/zenazn/goji/graceful" @@ -65,7 +66,8 @@ var staticBox *rice.Box var timeStarted time.Time var timeStartedStr string var remoteAuthKeys []string -var metaBackend backends.StorageBackend +var metaStorageBackend backends.MetaStorageBackend +var metaBackend backends.MetaBackend var fileBackend backends.StorageBackend func setup() *web.Mux { @@ -124,7 +126,8 @@ func setup() *web.Mux { Config.sitePath = "/" } - metaBackend = localfs.NewLocalfsBackend(Config.metaDir) + metaStorageBackend = localfs.NewLocalfsBackend(Config.metaDir) + metaBackend = metajson.NewMetaJSONBackend(metaStorageBackend) fileBackend = localfs.NewLocalfsBackend(Config.filesDir) // Template setup diff --git a/torrent.go b/torrent.go index f0f4d48..23361b5 100644 --- a/torrent.go +++ b/torrent.go @@ -8,6 +8,7 @@ import ( "net/http" "time" + "github.com/andreimarcu/linx-server/backends" "github.com/zeebo/bencode" "github.com/zenazn/goji/web" ) @@ -74,7 +75,7 @@ func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) { if err == NotFoundErr { notFoundHandler(c, w, r) return - } else if err == BadMetadata { + } else if err == backends.BadMetadata { oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") return } diff --git a/upload.go b/upload.go index 9cf8713..b7195c3 100644 --- a/upload.go +++ b/upload.go @@ -15,6 +15,8 @@ import ( "strings" "time" + "github.com/andreimarcu/linx-server/backends" + "github.com/andreimarcu/linx-server/expiry" "github.com/dchest/uniuri" "github.com/zenazn/goji/web" "gopkg.in/h2non/filetype.v1" @@ -41,7 +43,7 @@ type UploadRequest struct { // Metadata associated with a file as it would actually be stored type Upload struct { Filename string // Final filename on disk - Metadata Metadata + Metadata backends.Metadata } func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { @@ -258,11 +260,11 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { } // Get the rest of the metadata needed for storage - var expiry time.Time + var fileExpiry time.Time if upReq.expiry == 0 { - expiry = neverExpire + fileExpiry = expiry.NeverExpire } else { - expiry = time.Now().Add(upReq.expiry) + fileExpiry = time.Now().Add(upReq.expiry) } bytes, err := fileBackend.Put(upload.Filename, io.MultiReader(bytes.NewReader(header), upReq.src)) @@ -273,7 +275,7 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { return upload, errors.New("File too large") } - upload.Metadata, err = generateMetadata(upload.Filename, expiry, upReq.deletionKey) + upload.Metadata, err = generateMetadata(upload.Filename, fileExpiry, upReq.deletionKey) if err != nil { fileBackend.Delete(upload.Filename) return @@ -342,14 +344,14 @@ func parseExpiry(expStr string) time.Duration { if expStr == "" { return time.Duration(Config.maxExpiry) * time.Second } else { - expiry, err := strconv.ParseUint(expStr, 10, 64) + fileExpiry, err := strconv.ParseUint(expStr, 10, 64) if err != nil { return time.Duration(Config.maxExpiry) * time.Second } else { - if Config.maxExpiry > 0 && (expiry > Config.maxExpiry || expiry == 0) { - expiry = Config.maxExpiry + if Config.maxExpiry > 0 && (fileExpiry > Config.maxExpiry || fileExpiry == 0) { + fileExpiry = Config.maxExpiry } - return time.Duration(expiry) * time.Second + return time.Duration(fileExpiry) * time.Second } } } From c6f62fccdf8cfa2926e4cd18eb11be225fa1e5f9 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Mon, 1 May 2017 21:49:27 -0700 Subject: [PATCH 035/233] Drop Mercurial from Dockerfile We no longer have any Mercurial dependencies, so we don't need to install it anymore. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6ff81bd..60e189d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM golang:alpine RUN set -ex \ - && apk add --no-cache --virtual .build-deps git mercurial \ + && apk add --no-cache --virtual .build-deps git \ && go get github.com/andreimarcu/linx-server \ && apk del .build-deps From 32b537a057a3e1fe3c8632ba2c32c314fabd3deb Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Wed, 3 May 2017 21:12:41 -0700 Subject: [PATCH 036/233] Add missing `flag.Parse()` call --- linx-cleanup/cleanup.go | 1 + 1 file changed, 1 insertion(+) diff --git a/linx-cleanup/cleanup.go b/linx-cleanup/cleanup.go index 7607fe6..a8dc37f 100644 --- a/linx-cleanup/cleanup.go +++ b/linx-cleanup/cleanup.go @@ -20,6 +20,7 @@ func main() { "path to metadata directory") flag.BoolVar(&noLogs, "nologs", false, "don't log deleted files") + flag.Parse() metaStorageBackend := localfs.NewLocalfsBackend(metaDir) metaBackend := metajson.NewMetaJSONBackend(metaStorageBackend) From a69aa95a87061e575fc5919a678029649fb85bbe Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Thu, 4 May 2017 21:43:52 -0700 Subject: [PATCH 037/233] Add `linx-cleanup` info to readme --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 1921855..7fa6b68 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,23 @@ allowhotlink = true A helper utility ```linx-genkey``` is provided which hashes keys to the format required in the auth files. +Cleaning up expired files +------------------------- +When files expire, access is disabled immediately, but the files and metadata +will persist on disk until someone attempts to access them. If you'd like to +automatically clean up files that have expired, you can use the included +`linx-cleanup` utility. To run it automatically, use a cronjob or similar type +of scheduled task. + +You should be careful to ensure that only one instance of `linx-client` runs at +a time to avoid unexpected behavior. It does not implement any type of locking. + +#### Options +- ```-filespath files/``` -- Path to stored uploads (default is files/) +- ```-metapath meta/``` -- Path to stored information about uploads (default is meta/) +- ```-nologs``` -- (optionally) disable deletion logs in stdout + + Deployment ---------- Linx-server supports being deployed in a subdirectory (ie. example.com/mylinx/) as well as on its own (example.com/). From e86f19ecb1d674c964aa7af3f99f65f40529431d Mon Sep 17 00:00:00 2001 From: andreimarcu Date: Tue, 10 Oct 2017 22:14:53 -0700 Subject: [PATCH 038/233] Add linx-cleanup to build script --- build.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/build.sh b/build.sh index 9edffd5..f7a4115 100755 --- a/build.sh +++ b/build.sh @@ -64,3 +64,31 @@ GOOS=windows GOARCH=amd64 go build -o "$name"windows-amd64.exe GOOS=windows GOARCH=386 go build -o "$name"windows-386.exe cd .. + + +cd linx-cleanup +name="../binairies/""$version""/linx-cleanup-v""$version""_" + +GOOS=darwin GOARCH=amd64 go build -o "$name"osx-amd64 + +GOOS=darwin GOARCH=386 go build -o "$name"osx-386 + +GOOS=freebsd GOARCH=amd64 go build -o "$name"freebsd-amd64 + +GOOS=freebsd GOARCH=386 go build -o "$name"freebsd-386 + +GOOS=openbsd GOARCH=amd64 go build -o "$name"openbsd-amd64 + +GOOS=openbsd GOARCH=386 go build -o "$name"openbsd-386 + +GOOS=linux GOARCH=arm go build -o "$name"linux-arm + +GOOS=linux GOARCH=amd64 go build -o "$name"linux-amd64 + +GOOS=linux GOARCH=386 go build -o "$name"linux-386 + +GOOS=windows GOARCH=amd64 go build -o "$name"windows-amd64.exe + +GOOS=windows GOARCH=386 go build -o "$name"windows-386.exe + +cd .. From c015b5f1daa60e7bda4df9e84023c87e4a92656f Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sun, 25 Feb 2018 14:46:20 -0800 Subject: [PATCH 039/233] Fix -nologs on linx-cleanup tool (closes #136) --- linx-cleanup/cleanup.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/linx-cleanup/cleanup.go b/linx-cleanup/cleanup.go index a8dc37f..9ea89ae 100644 --- a/linx-cleanup/cleanup.go +++ b/linx-cleanup/cleanup.go @@ -34,11 +34,15 @@ func main() { for _, filename := range files { metadata, err := metaBackend.Get(filename) if err != nil { - log.Printf("Failed to find metadata for %s", filename) + if !noLogs { + log.Printf("Failed to find metadata for %s", filename) + } } if expiry.IsTsExpired(metadata.Expiry) { - log.Printf("Delete %s", filename) + if !noLogs { + log.Printf("Delete %s", filename) + } fileBackend.Delete(filename) metaStorageBackend.Delete(filename) } From b82371250a73037dfbf03d68bf01c89cf78ea1ee Mon Sep 17 00:00:00 2001 From: "Rodrigo D. L" Date: Sat, 21 Apr 2018 13:01:25 -0300 Subject: [PATCH 040/233] Fix files and meta permissions When running under docker-compose the volume changes the permissions by default to root but its avoided if the directory where it is mounted is created first with the correct owner. --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 60e189d..8f02dcc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,8 @@ RUN set -ex \ && go get github.com/andreimarcu/linx-server \ && apk del .build-deps +RUN mkdir -p /data/files && mkdir -p /data/meta && chown -R 65534:65534 /data + VOLUME ["/data/files", "/data/meta"] EXPOSE 8080 From 5d8a0ef605e535f838000ce21f0de581f287a133 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Wed, 7 Nov 2018 19:13:27 +0100 Subject: [PATCH 041/233] Serve file directly for curl and wget user agents (#145) * Serve file directly for curl and wget user agents Fix #127 * Add test for get with wget user agent * Add -nodirectagents flag to disable serving files directly for wget/curl user agents * Fix TestPutAndGetCLI failing for Go 1.5 It failed because it doesn't include the Content-Type header for every response. --- display.go | 8 ++++++++ server.go | 3 +++ server_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/display.go b/display.go index c6d8470..4220c76 100644 --- a/display.go +++ b/display.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -18,7 +19,14 @@ import ( const maxDisplayFileSizeBytes = 1024 * 512 +var cliUserAgentRe = regexp.MustCompile("(?i)(lib)?curl|wget") + func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { + if !Config.noDirectAgents && cliUserAgentRe.MatchString(r.Header.Get("User-Agent")) { + fileServeHandler(c, w, r) + return + } + fileName := c.URLParams["name"] err := checkFile(fileName) diff --git a/server.go b/server.go index daaf89e..ba0efb8 100644 --- a/server.go +++ b/server.go @@ -58,6 +58,7 @@ var Config struct { remoteAuthFile string addHeaders headerList googleShorterAPIKey string + noDirectAgents bool } var Templates = make(map[string]*pongo2.Template) @@ -243,6 +244,8 @@ func main() { "Add an arbitrary header to the response. This option can be used multiple times.") flag.StringVar(&Config.googleShorterAPIKey, "googleapikey", "", "API Key for Google's URL Shortener.") + flag.BoolVar(&Config.noDirectAgents, "nodirectagents", false, + "disable serving files directly for wget/curl user agents") iniflags.Parse() diff --git a/server_test.go b/server_test.go index 6fe363c..7727f0c 100644 --- a/server_test.go +++ b/server_test.go @@ -1121,3 +1121,50 @@ func TestShutdown(t *testing.T) { os.RemoveAll(Config.filesDir) os.RemoveAll(Config.metaDir) } + +func TestPutAndGetCLI(t *testing.T) { + var myjson RespOkJSON + mux := setup() + + // upload file + w := httptest.NewRecorder() + req, err := http.NewRequest("PUT", "/upload", strings.NewReader("File content")) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Accept", "application/json") + mux.ServeHTTP(w, req) + + err = json.Unmarshal([]byte(w.Body.String()), &myjson) + if err != nil { + t.Fatal(err) + } + + // request file without wget user agent + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", myjson.Url, nil) + if err != nil { + t.Fatal(err) + } + mux.ServeHTTP(w, req) + + contentType := w.Header().Get("Content-Type") + if strings.HasPrefix(contentType, "text/plain") { + t.Fatalf("Didn't receive file display page but %s", contentType) + } + + // request file with wget user agent + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", myjson.Url, nil) + req.Header.Set("User-Agent", "wget") + if err != nil { + t.Fatal(err) + } + mux.ServeHTTP(w, req) + + contentType = w.Header().Get("Content-Type") + if !strings.HasPrefix(contentType, "text/plain") { + t.Fatalf("Didn't receive file directly but %s", contentType) + } + +} From f19247a79059590b76c98274c24002917efef017 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Tue, 8 Jan 2019 11:18:57 -0800 Subject: [PATCH 042/233] Update Travis to 1.10 and 1.11 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index eaf499f..cd71721 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.5 - - 1.6 + - 1.10 + - 1.11 before_script: - go vet ./... From 19a95e36a6549313c0d3fbb44112d0417d327b45 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Tue, 8 Jan 2019 11:28:10 -0800 Subject: [PATCH 043/233] Fix Travis parsing of 1.10 as 1.1 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd71721..7baaebc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.10 - - 1.11 + - "1.10" + - "1.11" before_script: - go vet ./... From bad7d2666e053cb9bb1381e3d4f6fdf6107cf3f8 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Tue, 8 Jan 2019 19:56:09 +0000 Subject: [PATCH 044/233] Switch to Referrer-Policy header (#149) Use of the Content-Security-Policy header to specify a referrer policy was deprecated in favor of a [new header](https://github.com/w3c/webappsec-referrer-policy/commit/fc55d917bee0d5636f52a19a5aefa65f8995c766). This change changes the existing referrer policy directives to use this header and adds corresponding config options/command line flags. --- README.md | 6 ++++-- csp.go | 11 +++++++++-- csp_test.go | 11 +++++++---- fileserve.go | 1 + server.go | 17 +++++++++++++---- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7fa6b68..e1a5c8a 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,10 @@ allowhotlink = true - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) - ```-maxexpiry 86400``` -- maximum expiration time in seconds (default is 0, which is no expiry) - ```-allowhotlink``` -- Allow file hotlinking -- ```-contentsecuritypolicy "..."``` -- Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") -- ```-filecontentsecuritypolicy "..."``` -- Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;") +- ```-contentsecuritypolicy "..."``` -- Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") +- ```-filecontentsecuritypolicy "..."``` -- Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") +- ```-refererpolicy "..."``` -- Referrer-Policy header for pages (default is "same-origin") +- ```-filereferrerpolicy "..."``` -- Referrer-Policy header for files (default is "same-origin") - ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN") - ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...) - ```-nologs``` -- (optionally) disable request logs in stdout diff --git a/csp.go b/csp.go index 098e271..34b73b4 100644 --- a/csp.go +++ b/csp.go @@ -6,6 +6,7 @@ import ( const ( cspHeader = "Content-Security-Policy" + rpHeader = "Referrer-Policy" frameOptionsHeader = "X-Frame-Options" ) @@ -15,8 +16,9 @@ type csp struct { } type CSPOptions struct { - policy string - frame string + policy string + referrerPolicy string + frame string } func (c csp) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -25,6 +27,11 @@ func (c csp) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Add(cspHeader, c.opts.policy) } + // only add a Referrer Policy if one is not already set + if existing := w.Header().Get(rpHeader); existing == "" { + w.Header().Add(rpHeader, c.opts.referrerPolicy) + } + w.Header().Set(frameOptionsHeader, c.opts.frame) c.h.ServeHTTP(w, r) diff --git a/csp_test.go b/csp_test.go index 190c65d..3d1d499 100644 --- a/csp_test.go +++ b/csp_test.go @@ -12,6 +12,7 @@ import ( var testCSPHeaders = map[string]string{ "Content-Security-Policy": "default-src 'none'; style-src 'self';", + "Referrer-Policy": "strict-origin-when-cross-origin", "X-Frame-Options": "SAMEORIGIN", } @@ -22,8 +23,9 @@ func TestContentSecurityPolicy(t *testing.T) { Config.maxSize = 1024 * 1024 * 1024 Config.noLogs = true Config.siteName = "linx" - Config.contentSecurityPolicy = "default-src 'none'; style-src 'self';" - Config.xFrameOptions = "SAMEORIGIN" + Config.contentSecurityPolicy = testCSPHeaders["Content-Security-Policy"] + Config.referrerPolicy = testCSPHeaders["Referrer-Policy"] + Config.xFrameOptions = testCSPHeaders["X-Frame-Options"] mux := setup() w := httptest.NewRecorder() @@ -34,8 +36,9 @@ func TestContentSecurityPolicy(t *testing.T) { } goji.Use(ContentSecurityPolicy(CSPOptions{ - policy: testCSPHeaders["Content-Security-Policy"], - frame: testCSPHeaders["X-Frame-Options"], + policy: testCSPHeaders["Content-Security-Policy"], + referrerPolicy: testCSPHeaders["Referrer-Policy"], + frame: testCSPHeaders["X-Frame-Options"], })) mux.ServeHTTP(w, req) diff --git a/fileserve.go b/fileserve.go index f7f87a6..951bea2 100644 --- a/fileserve.go +++ b/fileserve.go @@ -32,6 +32,7 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Security-Policy", Config.fileContentSecurityPolicy) + w.Header().Set("Referrer-Policy", Config.fileReferrerPolicy) fileBackend.ServeFile(fileName, w, r) } diff --git a/server.go b/server.go index ba0efb8..5280503 100644 --- a/server.go +++ b/server.go @@ -46,6 +46,8 @@ var Config struct { keyFile string contentSecurityPolicy string fileContentSecurityPolicy string + referrerPolicy string + fileReferrerPolicy string xFrameOptions string maxSize int64 maxExpiry uint64 @@ -88,8 +90,9 @@ func setup() *web.Mux { mux.Use(middleware.Recoverer) mux.Use(middleware.AutomaticOptions) mux.Use(ContentSecurityPolicy(CSPOptions{ - policy: Config.contentSecurityPolicy, - frame: Config.xFrameOptions, + policy: Config.contentSecurityPolicy, + referrerPolicy: Config.referrerPolicy, + frame: Config.xFrameOptions, })) mux.Use(AddHeaders(Config.addHeaders)) @@ -233,11 +236,17 @@ func main() { flag.StringVar(&Config.remoteAuthFile, "remoteauthfile", "", "path to a file containing newline-separated scrypted auth keys for remote uploads") flag.StringVar(&Config.contentSecurityPolicy, "contentsecuritypolicy", - "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;", + "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';", "value of default Content-Security-Policy header") flag.StringVar(&Config.fileContentSecurityPolicy, "filecontentsecuritypolicy", - "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; referrer origin;", + "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';", "value of Content-Security-Policy header for file access") + flag.StringVar(&Config.referrerPolicy, "referrerpolicy", + "same-origin", + "value of default Referrer-Policy header") + flag.StringVar(&Config.fileReferrerPolicy, "filereferrerpolicy", + "same-origin", + "value of Referrer-Policy header for file access") flag.StringVar(&Config.xFrameOptions, "xframeoptions", "SAMEORIGIN", "value of X-Frame-Options header") flag.Var(&Config.addHeaders, "addheader", From 10938a3e0b72ab6f0d66381ca2e4fa7ff339140f Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Tue, 8 Jan 2019 19:56:32 +0000 Subject: [PATCH 045/233] Remove Google URL shortener (fix #146) (#150) --- README.md | 1 - backends/meta.go | 1 - backends/metajson/metajson.go | 3 -- display.go | 16 +++---- server.go | 8 ---- shorturl.go | 89 ----------------------------------- static/js/shorturl.js | 39 --------------- templates/display/base.html | 13 ----- 8 files changed, 7 insertions(+), 163 deletions(-) delete mode 100644 shorturl.go delete mode 100644 static/js/shorturl.js diff --git a/README.md b/README.md index e1a5c8a..df13b18 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ allowhotlink = true - ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN") - ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...) - ```-nologs``` -- (optionally) disable request logs in stdout -- ```-googleapikey``` -- (optionally) API Key for Google's URL Shortener. ([How to create one](https://developers.google.com/url-shortener/v1/getting_started#APIKey)) #### SSL with built-in server - ```-certfile path/to/your.crt``` -- Path to the ssl certificate (required if you want to use the https server) diff --git a/backends/meta.go b/backends/meta.go index eb17d5e..27c3e41 100644 --- a/backends/meta.go +++ b/backends/meta.go @@ -17,7 +17,6 @@ type Metadata struct { Size int64 Expiry time.Time ArchiveFiles []string - ShortURL string } var BadMetadata = errors.New("Corrupted metadata.") diff --git a/backends/metajson/metajson.go b/backends/metajson/metajson.go index 6e76dd4..8ec53c4 100644 --- a/backends/metajson/metajson.go +++ b/backends/metajson/metajson.go @@ -15,7 +15,6 @@ type MetadataJSON struct { Size int64 `json:"size"` Expiry int64 `json:"expiry"` ArchiveFiles []string `json:"archive_files,omitempty"` - ShortURL string `json:"short_url"` } type MetaJSONBackend struct { @@ -30,7 +29,6 @@ func (m MetaJSONBackend) Put(key string, metadata *backends.Metadata) error { mjson.Sha256sum = metadata.Sha256sum mjson.Expiry = metadata.Expiry.Unix() mjson.Size = metadata.Size - mjson.ShortURL = metadata.ShortURL byt, err := json.Marshal(mjson) if err != nil { @@ -63,7 +61,6 @@ func (m MetaJSONBackend) Get(key string) (metadata backends.Metadata, err error) metadata.Sha256sum = mjson.Sha256sum metadata.Expiry = time.Unix(mjson.Expiry, 0) metadata.Size = mjson.Size - metadata.ShortURL = mjson.ShortURL return } diff --git a/display.go b/display.go index 4220c76..c17fea6 100644 --- a/display.go +++ b/display.go @@ -116,15 +116,13 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } err = renderTemplate(tpl, pongo2.Context{ - "mime": metadata.Mimetype, - "filename": fileName, - "size": sizeHuman, - "expiry": expiryHuman, - "extra": extra, - "lines": lines, - "files": metadata.ArchiveFiles, - "shorturlEnabled": Config.googleShorterAPIKey != "", - "shorturl": metadata.ShortURL, + "mime": metadata.Mimetype, + "filename": fileName, + "size": sizeHuman, + "expiry": expiryHuman, + "extra": extra, + "lines": lines, + "files": metadata.ArchiveFiles, }, r, w) if err != nil { diff --git a/server.go b/server.go index 5280503..1dd1f09 100644 --- a/server.go +++ b/server.go @@ -59,7 +59,6 @@ var Config struct { authFile string remoteAuthFile string addHeaders headerList - googleShorterAPIKey string noDirectAgents bool } @@ -154,7 +153,6 @@ func setup() *web.Mux { selifRe := regexp.MustCompile("^" + Config.sitePath + `selif/(?P[a-z0-9-\.]+)$`) selifIndexRe := regexp.MustCompile("^" + Config.sitePath + `selif/$`) torrentRe := regexp.MustCompile("^" + Config.sitePath + `(?P[a-z0-9-\.]+)/torrent$`) - shortRe := regexp.MustCompile("^" + Config.sitePath + `(?P[a-z0-9-\.]+)/short$`) if Config.authFile == "" { mux.Get(Config.sitePath, indexHandler) @@ -193,10 +191,6 @@ func setup() *web.Mux { mux.Get(selifIndexRe, unauthorizedHandler) mux.Get(torrentRe, fileTorrentHandler) - if Config.googleShorterAPIKey != "" { - mux.Get(shortRe, shortURLHandler) - } - mux.NotFound(notFoundHandler) return mux @@ -251,8 +245,6 @@ func main() { "value of X-Frame-Options header") flag.Var(&Config.addHeaders, "addheader", "Add an arbitrary header to the response. This option can be used multiple times.") - flag.StringVar(&Config.googleShorterAPIKey, "googleapikey", "", - "API Key for Google's URL Shortener.") flag.BoolVar(&Config.noDirectAgents, "nodirectagents", false, "disable serving files directly for wget/curl user agents") diff --git a/shorturl.go b/shorturl.go deleted file mode 100644 index afdaf00..0000000 --- a/shorturl.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "errors" - "net/http" - - "github.com/zenazn/goji/web" -) - -type shortenerRequest struct { - LongURL string `json:"longUrl"` -} - -type shortenerResponse struct { - Kind string `json:"kind"` - ID string `json:"id"` - LongURL string `json:"longUrl"` - Error struct { - Code int `json:"code"` - Message string `json:"message"` - } `json:"error"` -} - -func shortURLHandler(c web.C, w http.ResponseWriter, r *http.Request) { - fileName := c.URLParams["name"] - - err := checkFile(fileName) - if err == NotFoundErr { - notFoundHandler(c, w, r) - return - } - - metadata, err := metadataRead(fileName) - if err != nil { - oopsHandler(c, w, r, RespJSON, "Corrupt metadata.") - return - } - - if metadata.ShortURL == "" { - url, err := shortenURL(getSiteURL(r) + fileName) - if err != nil { - oopsHandler(c, w, r, RespJSON, err.Error()) - return - } - - metadata.ShortURL = url - - err = metadataWrite(fileName, &metadata) - if err != nil { - oopsHandler(c, w, r, RespJSON, "Corrupt metadata.") - return - } - } - - js, _ := json.Marshal(map[string]string{ - "shortUrl": metadata.ShortURL, - }) - w.Write(js) - return -} - -func shortenURL(url string) (string, error) { - apiURL := "https://www.googleapis.com/urlshortener/v1/url?key=" + Config.googleShorterAPIKey - jsonStr, _ := json.Marshal(shortenerRequest{LongURL: url}) - - req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonStr)) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - shortenerResponse := new(shortenerResponse) - err = json.NewDecoder(resp.Body).Decode(shortenerResponse) - if err != nil { - return "", err - } - - if shortenerResponse.Error.Message != "" { - return "", errors.New(shortenerResponse.Error.Message) - } - - return shortenerResponse.ID, nil -} diff --git a/static/js/shorturl.js b/static/js/shorturl.js deleted file mode 100644 index 26e0c77..0000000 --- a/static/js/shorturl.js +++ /dev/null @@ -1,39 +0,0 @@ -document.getElementById('shorturl').addEventListener('click', function (e) { - e.preventDefault(); - - if (e.target.href !== "") return; - - xhr = new XMLHttpRequest(); - xhr.open("GET", e.target.dataset.url, true); - xhr.setRequestHeader('Accept', 'application/json'); - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - var resp = JSON.parse(xhr.responseText); - - if (xhr.status === 200 && resp.error == null) { - e.target.innerText = resp.shortUrl; - e.target.href = resp.shortUrl; - e.target.setAttribute('aria-label', 'Click to copy into clipboard') - } else { - e.target.setAttribute('aria-label', resp.error) - } - } - }; - xhr.send(); -}); - -var clipboard = new Clipboard("#shorturl", { - text: function (trigger) { - if (trigger.href == null) return; - - return trigger.href; - } -}); - -clipboard.on('success', function (e) { - e.trigger.setAttribute('aria-label', 'Successfully copied') -}); - -clipboard.on('error', function (e) { - e.trigger.setAttribute('aria-label', 'Your browser does not support coping to clipboard') -}); diff --git a/templates/display/base.html b/templates/display/base.html index 587e76f..8f33b46 100644 --- a/templates/display/base.html +++ b/templates/display/base.html @@ -17,15 +17,6 @@ {% endif %} {% block infomore %}{% endblock %} {{ size }} | - {% if shorturlEnabled %} - {% if shorturl %} - {{shorturl}} | - {% else %} - short url | - {% endif %} - {% endif %} torrent | get @@ -43,8 +34,4 @@ - - {% if shorturlEnabled %} - - {% endif %} {% endblock %} From 5f4f16e08be7935eba4d4c465ae97eb10aa1af53 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Wed, 9 Jan 2019 04:28:01 +0000 Subject: [PATCH 046/233] Add file ETag support (fix #138) (#152) --- display.go | 2 +- fileserve.go | 27 ++++++++++++++++++--------- torrent.go | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/display.go b/display.go index c17fea6..d897e5a 100644 --- a/display.go +++ b/display.go @@ -29,7 +29,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - err := checkFile(fileName) + _, err := checkFile(fileName) if err == NotFoundErr { notFoundHandler(c, w, r) return diff --git a/fileserve.go b/fileserve.go index 951bea2..3b20c4c 100644 --- a/fileserve.go +++ b/fileserve.go @@ -6,19 +6,23 @@ import ( "strings" "github.com/andreimarcu/linx-server/backends" + "github.com/andreimarcu/linx-server/expiry" "github.com/zenazn/goji/web" ) func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - err := checkFile(fileName) + metadata, err := checkFile(fileName) if err == NotFoundErr { notFoundHandler(c, w, r) return } else if err == backends.BadMetadata { oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") return + } else if err != nil { + oopsHandler(c, w, r, RespAUTO, err.Error()) + return } if !Config.allowHotlink { @@ -34,6 +38,9 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Security-Policy", Config.fileContentSecurityPolicy) w.Header().Set("Referrer-Policy", Config.fileReferrerPolicy) + w.Header().Set("Etag", metadata.Sha256sum) + w.Header().Set("Cache-Control", "max-age=0") + fileBackend.ServeFile(fileName, w, r) } @@ -61,22 +68,24 @@ func staticHandler(c web.C, w http.ResponseWriter, r *http.Request) { } } -func checkFile(filename string) error { - _, err := fileBackend.Exists(filename) +func checkFile(filename string) (metadata backends.Metadata, err error) { + _, err = fileBackend.Exists(filename) if err != nil { - return NotFoundErr + err = NotFoundErr + return } - expired, err := isFileExpired(filename) + metadata, err = metadataRead(filename) if err != nil { - return err + return } - if expired { + if expiry.IsTsExpired(metadata.Expiry) { fileBackend.Delete(filename) metaStorageBackend.Delete(filename) - return NotFoundErr + err = NotFoundErr + return } - return nil + return } diff --git a/torrent.go b/torrent.go index 23361b5..77cd2fd 100644 --- a/torrent.go +++ b/torrent.go @@ -71,7 +71,7 @@ func createTorrent(fileName string, f io.ReadCloser, r *http.Request) ([]byte, e func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - err := checkFile(fileName) + _, err := checkFile(fileName) if err == NotFoundErr { notFoundHandler(c, w, r) return From 6290f408ff7e995e24b39094ad1fec9449c8da2f Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Fri, 11 Jan 2019 18:09:54 +0100 Subject: [PATCH 047/233] Allow to paste images (#153) dropzone.js doesn't support pasting itself yet, so adding it externally and calling `.addFile()` to upload the pasted image. Fixes #130 --- static/js/upload.js | 12 +++++++++++- templates/index.html | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/static/js/upload.js b/static/js/upload.js index 5e54d62..159bad2 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -102,8 +102,18 @@ Dropzone.options.dropzone = { previewsContainer: "#uploads", parallelUploads: 5, headers: {"Accept": "application/json"}, - dictDefaultMessage: "Click or Drop file(s)", + dictDefaultMessage: "Click or Drop file(s) or Paste image", dictFallbackMessage: "" }; +document.onpaste = function(event) { + var items = (event.clipboardData || event.originalEvent.clipboardData).items; + for (index in items) { + var item = items[index]; + if (item.kind === "file") { + Dropzone.forElement("#dropzone").addFile(item.getAsFile()); + } + } +}; + // @end-license diff --git a/templates/index.html b/templates/index.html index 5e95d01..87d821d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -13,7 +13,7 @@
- Click or Drop file(s) + Click or Drop file(s) or Paste image
From c746f70c10e1406a1d59c20086ef962e9c398e4f Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Mon, 14 Jan 2019 14:51:02 -0800 Subject: [PATCH 048/233] Allow changing the "selif" path name --- README.md | 1 + csp_test.go | 1 + server.go | 12 ++++++++++-- server_test.go | 4 ++-- templates.go | 1 + templates/display/audio.html | 4 ++-- templates/display/base.html | 2 +- templates/display/file.html | 2 +- templates/display/image.html | 4 ++-- templates/display/pdf.html | 4 ++-- templates/display/video.html | 4 ++-- torrent.go | 2 +- torrent_test.go | 2 +- 13 files changed, 27 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index df13b18..8d4ef11 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ allowhotlink = true - ```-bind 127.0.0.1:8080``` -- what to bind to (default is 127.0.0.1:8080) - ```-sitename myLinx``` -- the site name displayed on top (default is inferred from Host header) - ```-siteurl "http://mylinx.example.org/"``` -- the site url (default is inferred from execution context) +- ```-selifpath "selif"``` -- path relative to site base url (the "selif" in https://mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) - ```-filespath files/``` -- Path to store uploads (default is files/) - ```-metapath meta/``` -- Path to store information about uploads (default is meta/) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) diff --git a/csp_test.go b/csp_test.go index 3d1d499..e3dbbdd 100644 --- a/csp_test.go +++ b/csp_test.go @@ -23,6 +23,7 @@ func TestContentSecurityPolicy(t *testing.T) { Config.maxSize = 1024 * 1024 * 1024 Config.noLogs = true Config.siteName = "linx" + Config.selifPath = "selif" Config.contentSecurityPolicy = testCSPHeaders["Content-Security-Policy"] Config.referrerPolicy = testCSPHeaders["Referrer-Policy"] Config.xFrameOptions = testCSPHeaders["X-Frame-Options"] diff --git a/server.go b/server.go index 1dd1f09..c7b4342 100644 --- a/server.go +++ b/server.go @@ -42,6 +42,7 @@ var Config struct { siteName string siteURL string sitePath string + selifPath string certFile string keyFile string contentSecurityPolicy string @@ -129,6 +130,11 @@ func setup() *web.Mux { Config.sitePath = "/" } + Config.selifPath = strings.TrimLeft(Config.selifPath, "/") + if lastChar := Config.selifPath[len(Config.selifPath)-1:]; lastChar != "/" { + Config.selifPath = Config.selifPath + "/" + } + metaStorageBackend = localfs.NewLocalfsBackend(Config.metaDir) metaBackend = metajson.NewMetaJSONBackend(metaStorageBackend) fileBackend = localfs.NewLocalfsBackend(Config.filesDir) @@ -150,8 +156,8 @@ func setup() *web.Mux { // Routing setup nameRe := regexp.MustCompile("^" + Config.sitePath + `(?P[a-z0-9-\.]+)$`) - selifRe := regexp.MustCompile("^" + Config.sitePath + `selif/(?P[a-z0-9-\.]+)$`) - selifIndexRe := regexp.MustCompile("^" + Config.sitePath + `selif/$`) + selifRe := regexp.MustCompile("^" + Config.sitePath + Config.selifPath + `(?P[a-z0-9-\.]+)$`) + selifIndexRe := regexp.MustCompile("^" + Config.sitePath + Config.selifPath + `$`) torrentRe := regexp.MustCompile("^" + Config.sitePath + `(?P[a-z0-9-\.]+)/torrent$`) if Config.authFile == "" { @@ -211,6 +217,8 @@ func main() { "name of the site") flag.StringVar(&Config.siteURL, "siteurl", "", "site base url (including trailing slash)") + flag.StringVar(&Config.selifPath, "selifpath", "selif", + "path relative to site base url where files are accessed directly") flag.Int64Var(&Config.maxSize, "maxsize", 4*1024*1024*1024, "maximum upload file size in bytes (default 4GB)") flag.Uint64Var(&Config.maxExpiry, "maxexpiry", 0, diff --git a/server_test.go b/server_test.go index 7727f0c..d9374ab 100644 --- a/server_test.go +++ b/server_test.go @@ -173,7 +173,7 @@ func TestFileNotFound(t *testing.T) { filename := generateBarename() - req, err := http.NewRequest("GET", "/selif/"+filename, nil) + req, err := http.NewRequest("GET", "/"+Config.selifPath+filename, nil) if err != nil { t.Fatal(err) } @@ -941,7 +941,7 @@ func TestPutAndOverwrite(t *testing.T) { // Make sure it's the new file w = httptest.NewRecorder() - req, err = http.NewRequest("GET", "/selif/"+myjson.Filename, nil) + req, err = http.NewRequest("GET", "/"+Config.selifPath+myjson.Filename, nil) mux.ServeHTTP(w, req) if w.Code == 404 { diff --git a/templates.go b/templates.go index 0687bce..79c90ce 100644 --- a/templates.go +++ b/templates.go @@ -83,6 +83,7 @@ func renderTemplate(tpl *pongo2.Template, context pongo2.Context, r *http.Reques } context["sitepath"] = Config.sitePath + context["selifpath"] = Config.selifPath context["using_auth"] = Config.authFile != "" return tpl.ExecuteWriter(context, writer) diff --git a/templates/display/audio.html b/templates/display/audio.html index 689bab3..b5ae1e3 100644 --- a/templates/display/audio.html +++ b/templates/display/audio.html @@ -2,8 +2,8 @@ {% block main %} {% endblock %} diff --git a/templates/display/base.html b/templates/display/base.html index 8f33b46..172e17a 100644 --- a/templates/display/base.html +++ b/templates/display/base.html @@ -18,7 +18,7 @@ {% block infomore %}{% endblock %} {{ size }} | torrent | - get + get
{% block infoleft %}{% endblock %} diff --git a/templates/display/file.html b/templates/display/file.html index 5eeb424..670651e 100644 --- a/templates/display/file.html +++ b/templates/display/file.html @@ -2,7 +2,7 @@ {% block main %}
-

You are requesting {{ filename }}, click here to download.

+

You are requesting {{ filename }}, click here to download.

{% if files|length > 0 %}

Contents of the archive:

diff --git a/templates/display/image.html b/templates/display/image.html index b1ea7dd..807b7ad 100644 --- a/templates/display/image.html +++ b/templates/display/image.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block main %} - - + + {% endblock %} diff --git a/templates/display/pdf.html b/templates/display/pdf.html index 7ad20f1..69501f7 100644 --- a/templates/display/pdf.html +++ b/templates/display/pdf.html @@ -1,10 +1,10 @@ {% extends "base.html" %} {% block main %} - +

It appears your Web browser is not configured to display PDF files. -No worries, just click here to download the PDF file.

+No worries, just click here to download the PDF file.

{% endblock %} diff --git a/templates/display/video.html b/templates/display/video.html index 9fc90d5..317664b 100644 --- a/templates/display/video.html +++ b/templates/display/video.html @@ -2,7 +2,7 @@ {% block main %} {% endblock %} diff --git a/torrent.go b/torrent.go index 77cd2fd..4155872 100644 --- a/torrent.go +++ b/torrent.go @@ -45,7 +45,7 @@ func createTorrent(fileName string, f io.ReadCloser, r *http.Request) ([]byte, e PieceLength: TORRENT_PIECE_LENGTH, Name: fileName, }, - UrlList: []string{fmt.Sprintf("%sselif/%s", getSiteURL(r), fileName)}, + UrlList: []string{fmt.Sprintf("%s%s%s", getSiteURL(r), Config.selifPath, fileName)}, } for { diff --git a/torrent_test.go b/torrent_test.go index 38132f2..b553231 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -45,7 +45,7 @@ func TestCreateTorrent(t *testing.T) { t.Fatal("Length was less than or equal to 0, expected more") } - tracker := fmt.Sprintf("%sselif/%s", Config.siteURL, fileName) + tracker := fmt.Sprintf("%s%s%s", Config.siteURL, Config.selifPath, fileName) if decoded.UrlList[0] != tracker { t.Fatalf("First entry in URL list was %s, expected %s", decoded.UrlList[0], tracker) } From 1fb92ffce30270ba46f7d7ba6aa0620b518edda6 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Mon, 14 Jan 2019 15:23:37 -0800 Subject: [PATCH 049/233] Fix bug where using curl with json headers would return the file instead --- display.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/display.go b/display.go index d897e5a..70d8dee 100644 --- a/display.go +++ b/display.go @@ -22,7 +22,7 @@ const maxDisplayFileSizeBytes = 1024 * 512 var cliUserAgentRe = regexp.MustCompile("(?i)(lib)?curl|wget") func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { - if !Config.noDirectAgents && cliUserAgentRe.MatchString(r.Header.Get("User-Agent")) { + if !Config.noDirectAgents && cliUserAgentRe.MatchString(r.Header.Get("User-Agent")) && !strings.EqualFold("application/json", r.Header.Get("Accept")) { fileServeHandler(c, w, r) return } From e506304b844d67f3860fd127605beb8ba3bc2244 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Mon, 14 Jan 2019 15:23:56 -0800 Subject: [PATCH 050/233] Return direct URL in json responses --- display.go | 11 ++++++----- upload.go | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/display.go b/display.go index 70d8dee..ca5bc6f 100644 --- a/display.go +++ b/display.go @@ -52,11 +52,12 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { if strings.EqualFold("application/json", r.Header.Get("Accept")) { js, _ := json.Marshal(map[string]string{ - "filename": fileName, - "expiry": strconv.FormatInt(metadata.Expiry.Unix(), 10), - "size": strconv.FormatInt(metadata.Size, 10), - "mimetype": metadata.Mimetype, - "sha256sum": metadata.Sha256sum, + "filename": fileName, + "direct_url": getSiteURL(r) + Config.selifPath + fileName, + "expiry": strconv.FormatInt(metadata.Expiry.Unix(), 10), + "size": strconv.FormatInt(metadata.Size, 10), + "mimetype": metadata.Mimetype, + "sha256sum": metadata.Sha256sum, }) w.Write(js) return diff --git a/upload.go b/upload.go index b7195c3..acdd204 100644 --- a/upload.go +++ b/upload.go @@ -295,6 +295,7 @@ func generateBarename() string { func generateJSONresponse(upload Upload, r *http.Request) []byte { js, _ := json.Marshal(map[string]string{ "url": getSiteURL(r) + upload.Filename, + "direct_url": getSiteURL(r) + Config.selifPath + upload.Filename, "filename": upload.Filename, "delete_key": upload.Metadata.DeleteKey, "expiry": strconv.FormatInt(upload.Metadata.Expiry.Unix(), 10), From 9d7f698c70be785a46dbe061ef649805c724c219 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Mon, 14 Jan 2019 16:16:15 -0800 Subject: [PATCH 051/233] Add direct_url info to API page --- templates/API.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/API.html b/templates/API.html index 045f4c2..64404b6 100644 --- a/templates/API.html +++ b/templates/API.html @@ -41,6 +41,7 @@

“url”: the publicly available upload url
+ “direct_url”: the url to access the file directly
“filename”: the (optionally generated) filename
“delete_key”: the (optionally generated) deletion key,
“expiry”: the unix timestamp at which the file will expire (0 if never)
@@ -121,6 +122,7 @@ DELETED

“url”: the publicly available upload url
+ “direct_url”: the url to access the file directly
“filename”: the (optionally generated) filename
“expiry”: the unix timestamp at which the file will expire (0 if never)
“size”: the size in bytes of the file
From 5340f23f4d32af329509c6e2fceb6600f85610d3 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Tue, 15 Jan 2019 06:41:03 +0000 Subject: [PATCH 052/233] Add new multi-stage slim Dockerfile (#154) --- Dockerfile | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8f02dcc..addad3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,26 @@ -FROM golang:alpine +FROM golang:alpine3.8 AS build + +COPY . /go/src/github.com/andreimarcu/linx-server +WORKDIR /go/src/github.com/andreimarcu/linx-server RUN set -ex \ && apk add --no-cache --virtual .build-deps git \ - && go get github.com/andreimarcu/linx-server \ + && go get -v . \ && apk del .build-deps +FROM alpine:3.8 + +COPY --from=build /go/bin/linx-server /usr/local/bin/linx-server + +ENV GOPATH /go +COPY static /go/src/github.com/andreimarcu/linx-server/static/ +COPY templates /go/src/github.com/andreimarcu/linx-server/templates/ + RUN mkdir -p /data/files && mkdir -p /data/meta && chown -R 65534:65534 /data VOLUME ["/data/files", "/data/meta"] EXPOSE 8080 USER nobody -ENTRYPOINT ["/go/bin/linx-server", "-bind=0.0.0.0:8080", "-filespath=/data/files/", "-metapath=/data/meta/"] +ENTRYPOINT ["/usr/local/bin/linx-server", "-bind=0.0.0.0:8080", "-filespath=/data/files/", "-metapath=/data/meta/"] CMD ["-sitename=linx", "-allowhotlink"] From fd0f3d9e46af8729e6c00d88d0726b7ed8159c79 Mon Sep 17 00:00:00 2001 From: Simon Alfassa Date: Sat, 9 Jun 2018 19:31:44 +0200 Subject: [PATCH 053/233] Make the web page mobile friendly --- static/css/dropzone.css | 9 +++++- static/css/github-markdown.css | 3 +- static/css/linx.css | 58 +++++++++++++++++++++------------- static/js/bin.js | 2 +- templates/404.html | 4 ++- templates/base.html | 1 + templates/display/base.html | 5 ++- templates/index.html | 4 +-- templates/paste.html | 10 +++--- 9 files changed, 60 insertions(+), 36 deletions(-) diff --git a/static/css/dropzone.css b/static/css/dropzone.css index 18472a6..f3c4cb2 100644 --- a/static/css/dropzone.css +++ b/static/css/dropzone.css @@ -31,11 +31,18 @@ border: 2px solid #FAFBFC; } -#dropzone { width: 400px; +#dropzone { + width: 400px; margin-left: auto; margin-right: auto; } +@media(max-width: 450px) { + #dropzone { + width: auto; + } +} + #uploads { margin-top: 20px; } diff --git a/static/css/github-markdown.css b/static/css/github-markdown.css index 8072b54..6823d9c 100644 --- a/static/css/github-markdown.css +++ b/static/css/github-markdown.css @@ -8,7 +8,8 @@ font-size: 12px; line-height: 1.6; word-wrap: break-word; - width: 680px; + width: 80vw; + max-width: 680px; padding: 10px; } diff --git a/static/css/linx.css b/static/css/linx.css index 193cbec..a759dc6 100644 --- a/static/css/linx.css +++ b/static/css/linx.css @@ -71,32 +71,26 @@ body { -webkit-box-shadow: 1px 1px 1px 1px #ccc; box-shadow: 1px 1px 1px 1px #ccc; margin-bottom: 15px; +} +.dinfo #filename { + margin: 2px 15px 0 0; } #info { - text-align: left; - + display: flex; + flex-wrap: wrap; + justify-content: space-between; background-color: white; padding: 5px 5px 5px 5px; } -#info #filename, -#editform #filename { - width: 232px; -} - #info #extension, #editform #extension { width: 40px; } -#info .float-left { - margin-top: 2px; - margin-right: 20px; -} - -#info .right { +#info .text-right { font-size: 13px; } @@ -115,6 +109,11 @@ body { color: #556A7F; } +#info input[type=checkbox] { + margin: 0; + vertical-align: bottom; +} + #footer { color: gray; text-align: right; @@ -158,7 +157,8 @@ body { } .fixed { - width: 800px; + width: 80vw; + max-width: 800px; } .needs-border { @@ -245,19 +245,28 @@ body { } #choices { - float: left; - width: 100%; - text-align: left; - vertical-align: bottom; - margin-top: 5px; + display: flex; + align-items: center; + flex-wrap: wrap; + justify-content: space-between; + width: 100%; + margin-top: 5px; font-size:13px; } +#choices label:first-child { + margin-right: 15px; +} + #expiry { - float: right; padding-top: 1px; } +#randomize { + vertical-align: bottom; + margin: 0; +} + .oopscontent { width: 400px; } @@ -267,8 +276,13 @@ body { border: 0; } +.error-404 img { + max-width: 90vw; +} + .editor { - width: 705px; + width: 90vw; + max-width: 705px; height: 450px; border-color: #cccccc; font-family: monospace; @@ -287,7 +301,7 @@ body { /* Content display {{{ */ .display-audio, .display-file { - width: 500px; + width: 100%; } .display-image { diff --git a/static/js/bin.js b/static/js/bin.js index 6e1bbcc..bcaf4f1 100644 --- a/static/js/bin.js +++ b/static/js/bin.js @@ -1,6 +1,6 @@ // @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later -var navlist = document.getElementById("info").getElementsByClassName("right")[0]; +var navlist = document.getElementById("info").getElementsByClassName("text-right")[0]; init(); diff --git a/templates/404.html b/templates/404.html index c1728e5..3b3d64e 100644 --- a/templates/404.html +++ b/templates/404.html @@ -1,5 +1,7 @@ {% extends "base.html" %} {% block content %} - +

+ +
{% endblock %} diff --git a/templates/base.html b/templates/base.html index d750cb4..5392f8d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,6 +3,7 @@ {% block title %}{{ sitename }}{% endblock %} + diff --git a/templates/display/base.html b/templates/display/base.html index 172e17a..29d21b8 100644 --- a/templates/display/base.html +++ b/templates/display/base.html @@ -7,11 +7,11 @@ {% block content %}
-
+
{{ filename }}
-
+
{% if expiry %} file expires in {{ expiry }} | {% endif %} @@ -22,7 +22,6 @@
{% block infoleft %}{% endblock %} -
diff --git a/templates/index.html b/templates/index.html index 87d821d..d423879 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,8 +17,9 @@
+
-
-
diff --git a/templates/paste.html b/templates/paste.html index 9178ba4..c4e88e9 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -4,17 +4,17 @@
- . - -
+
+ . +
+
+ - -
From b731e17c1ef56373f93eb760c45c0c1cf647cf8b Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Wed, 16 Jan 2019 01:15:24 -0800 Subject: [PATCH 054/233] Cosmetic tweaks & fixes --- display.go | 1 + static/css/dropzone.css | 1 + static/css/linx.css | 176 +++++++++++++++++++---------------- static/js/bin.js | 4 +- templates/display/base.html | 2 +- templates/display/bin.html | 20 ++-- templates/display/story.html | 20 ++-- templates/paste.html | 10 +- 8 files changed, 120 insertions(+), 114 deletions(-) diff --git a/display.go b/display.go index ca5bc6f..63ba765 100644 --- a/display.go +++ b/display.go @@ -121,6 +121,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { "filename": fileName, "size": sizeHuman, "expiry": expiryHuman, + "expirylist": listExpirationTimes(), "extra": extra, "lines": lines, "files": metadata.ArchiveFiles, diff --git a/static/css/dropzone.css b/static/css/dropzone.css index f3c4cb2..e19794f 100644 --- a/static/css/dropzone.css +++ b/static/css/dropzone.css @@ -49,6 +49,7 @@ div.dz-default { border: 2px dashed #C9C9C9; + border-radius: 5px; color: #C9C9C9; font: 14px "helvetica neue",helvetica,arial,sans-serif; background-color: #FAFBFC; diff --git a/static/css/linx.css b/static/css/linx.css index a759dc6..82e256a 100644 --- a/static/css/linx.css +++ b/static/css/linx.css @@ -1,56 +1,56 @@ body { - background-color: #E8ECF0; - color: #556A7F; + background-color: #E8ECF0; + color: #556A7F; - font-family: Arial, Helvetica, sans-serif; - font-size: 14px; + font-family: Arial, Helvetica, sans-serif; + font-size: 14px; } #container_container { - display: table; - table-layout: fixed; - margin-left: auto; - margin-right: auto; + display: table; + table-layout: fixed; + margin-left: auto; + margin-right: auto; } #container { - display: table-cell; - min-width: 200px; + display: table-cell; + min-width: 200px; } #header a { - text-decoration: none; - color: #556A7F; + text-decoration: none; + color: #556A7F; } #navigation { - margin-top: 4px; + margin-top: 4px; } #navigation a { - text-decoration: none; - border-bottom: 1px dotted #556A7F; - color: #556A7F; + text-decoration: none; + border-bottom: 1px dotted #556A7F; + color: #556A7F; } #navigation a:hover { - background-color: #C7D1EB; + background-color: #C7D1EB; } #main { - background-color: white; + background-color: white; - padding: 6px 5px 8px 5px; + padding: 6px 5px 8px 5px; - -moz-box-shadow: 1px 1px 1px 1px #ccc; - -webkit-box-shadow: 1px 1px 1px 1px #ccc; - box-shadow: 1px 1px 1px 1px #ccc; + -moz-box-shadow: 1px 1px 1px 1px #ccc; + -webkit-box-shadow: 1px 1px 1px 1px #ccc; + box-shadow: 1px 1px 1px 1px #ccc; - text-align: center; + text-align: center; } #main a { - color: #556A7F; + color: #556A7F; } #normal-content { @@ -62,10 +62,6 @@ body { margin-bottom: 0; } -.ninfo { - margin-bottom: 5px; -} - .dinfo { -moz-box-shadow: 1px 1px 1px 1px #ccc; -webkit-box-shadow: 1px 1px 1px 1px #ccc; @@ -73,16 +69,12 @@ body { margin-bottom: 15px; } -.dinfo #filename { - margin: 2px 15px 0 0; -} - #info { - display: flex; - flex-wrap: wrap; - justify-content: space-between; + display: flex; + flex-wrap: wrap; + justify-content: space-between; background-color: white; - padding: 5px 5px 5px 5px; + padding: 5px 5px 5px 5px; } #info #extension, @@ -91,7 +83,7 @@ body { } #info .text-right { - font-size: 13px; + font-size: 13px; } #info a { @@ -104,56 +96,50 @@ body { background-color: #E8ECF0; } -#info input[type=text] { - border: 0; - color: #556A7F; -} - #info input[type=checkbox] { - margin: 0; - vertical-align: bottom; + margin: 0; + vertical-align: bottom; } #footer { - color: gray; - text-align: right; - margin-top: 30px; - margin-bottom: 10px; - font-size: 11px; + color: gray; + text-align: right; + margin-top: 30px; + margin-bottom: 10px; + font-size: 11px; } #footer a { - color: gray; - text-decoration: none; + color: gray; + text-decoration: none; } - .normal { - text-align: left; - font-size: 13px; + text-align: left; + font-size: 13px; } .normal a { - text-decoration: none; - border-bottom: 1px dotted gray; + text-decoration: none; + border-bottom: 1px dotted gray; } .normal a:hover { - color: black; - background-color: #E8ECF0; + color: black; + background-color: #E8ECF0; } .normal ul { - padding-left: 15px; + padding-left: 15px; } .normal li { - margin-bottom: 3px; - list-style: none; + margin-bottom: 3px; + list-style: none; } .normal li a { - font-weight: bold; + font-weight: bold; } .fixed { @@ -161,37 +147,46 @@ body { max-width: 800px; } +.paste { + width: 70vw; + max-width: 700px; +} + .needs-border { - border-top: 1px solid rgb(214, 214, 214); + border-top: 1px solid rgb(214, 214, 214); } .left { - text-align: left; + text-align: left; } .float-left { - float: left; + float: left; +} + +.pad-left { + padding-left: 10px; } .pad-right { - padding-right: 10px; + padding-right: 10px; } .text-right { - text-align: right; + text-align: right; } .center { - text-align: center; + text-align: center; } .float-right, .right { - float: right; + float: right; } .clear { - clear: both; + clear: both; } #upload_header { @@ -255,7 +250,7 @@ body { } #choices label:first-child { - margin-right: 15px; + margin-right: 15px; } #expiry { @@ -280,14 +275,34 @@ body { max-width: 90vw; } +.padme { + padding-left: 5px; + padding-right: 5px; +} + .editor { - width: 90vw; - max-width: 705px; - height: 450px; - border-color: #cccccc; - font-family: monospace; - resize: none; - overflow: auto; + width: 100%; + height: 450px; + border: 1px solid #eaeaea; + font-family: monospace; + resize: none; + overflow: auto; + border-radius: 2px; + padding: 2px; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + } + + + +#info input[type=text] { + border: 1px solid #eaeaea; + color: #556A7F; + border-radius: 4px 4px 4px 4px; + padding-left: 4px; + padding-right: 4px; + height: 15px; } .storygreen { @@ -329,15 +344,16 @@ body { #editform, #editform .editor { display: none; + width: 100% } #codeb { white-space: pre-wrap; } -#editor { +#inplace-editor { display: none; - width: 794px; + width: 100%; height: 800px; font-size: 13px; } diff --git a/static/js/bin.js b/static/js/bin.js index bcaf4f1..7aed334 100644 --- a/static/js/bin.js +++ b/static/js/bin.js @@ -32,13 +32,13 @@ function edit(ev) { var normalcontent = document.getElementById("normal-content"); normalcontent.removeChild(document.getElementById("normal-code")); - var editordiv = document.getElementById("editor"); + var editordiv = document.getElementById("inplace-editor"); editordiv.style.display = "block"; editordiv.addEventListener('keydown', handleTab); } function paste(ev) { - var editordiv = document.getElementById("editor"); + var editordiv = document.getElementById("inplace-editor"); document.getElementById("newcontent").value = editordiv.value; document.forms["reply"].submit(); } diff --git a/templates/display/base.html b/templates/display/base.html index 29d21b8..011534c 100644 --- a/templates/display/base.html +++ b/templates/display/base.html @@ -11,7 +11,7 @@ {{ filename }}
-
+
{% if expiry %} file expires in {{ expiry }} | {% endif %} diff --git a/templates/display/bin.html b/templates/display/bin.html index 12e49c9..bd029a2 100644 --- a/templates/display/bin.html +++ b/templates/display/bin.html @@ -12,23 +12,17 @@ {% block infoleft %}
-
+
+ - -
- . + .
@@ -41,7 +35,7 @@ {% block main %}
{{ extra.contents }}
- +
diff --git a/templates/display/story.html b/templates/display/story.html index 763fd4a..20e772c 100644 --- a/templates/display/story.html +++ b/templates/display/story.html @@ -10,23 +10,17 @@ {% block infoleft %}
-
+
+ - -
- . + .
@@ -39,7 +33,7 @@ {% block main %}
{% for line in lines %}{% if line|make_list|first == ">" %}{{ line }}{% else %}{{ line }}{% endif %}{% endfor %}
- +
diff --git a/templates/paste.html b/templates/paste.html index c4e88e9..7737760 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -2,10 +2,10 @@ {% block content %}
-
-
+
+
- . + .
@@ -18,8 +18,8 @@
-
- +
+
From 0fb5fa1c517a12ca60d548ccadc45301d5bf6679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20S=C3=A4nger?= Date: Fri, 25 Jan 2019 08:21:49 +0100 Subject: [PATCH 055/233] use sha256-simd (#155) --- meta.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta.go b/meta.go index 10cd4e1..2fc7b81 100644 --- a/meta.go +++ b/meta.go @@ -5,7 +5,6 @@ import ( "archive/zip" "compress/bzip2" "compress/gzip" - "crypto/sha256" "encoding/hex" "errors" "io" @@ -16,6 +15,7 @@ import ( "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/expiry" "github.com/dchest/uniuri" + "github.com/minio/sha256-simd" "gopkg.in/h2non/filetype.v1" ) From 5d9a93b1e285fe36b827ec74a2186f8623408b25 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Fri, 25 Jan 2019 07:33:11 +0000 Subject: [PATCH 056/233] Add S3 backend (#156) --- Dockerfile | 2 + backends/localfs/localfs.go | 143 ++++++++++++++++++++----- backends/meta.go | 5 - backends/metajson/metajson.go | 70 ------------- backends/s3/s3.go | 192 ++++++++++++++++++++++++++++++++++ backends/storage.go | 20 ++-- delete.go | 19 ++-- display.go | 46 +++++--- expiry.go | 2 +- fileserve.go | 37 ++++--- helpers/archive.go | 70 +++++++++++++ helpers/helpers.go | 67 ++++++++++++ linx-cleanup/cleanup.go | 10 +- meta.go | 165 ----------------------------- pages.go | 35 +++++-- server.go | 22 ++-- server_test.go | 80 ++++++++++++-- torrent.go | 66 +++++------- torrent/torrent.go | 28 +++++ torrent_test.go | 5 +- upload.go | 95 +++++++++-------- 21 files changed, 738 insertions(+), 441 deletions(-) delete mode 100644 backends/metajson/metajson.go create mode 100644 backends/s3/s3.go create mode 100644 helpers/archive.go create mode 100644 helpers/helpers.go delete mode 100644 meta.go create mode 100644 torrent/torrent.go diff --git a/Dockerfile b/Dockerfile index addad3f..c1a2f2e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,8 @@ FROM alpine:3.8 COPY --from=build /go/bin/linx-server /usr/local/bin/linx-server ENV GOPATH /go +ENV SSL_CERT_FILE /etc/ssl/cert.pem + COPY static /go/src/github.com/andreimarcu/linx-server/static/ COPY templates /go/src/github.com/andreimarcu/linx-server/templates/ diff --git a/backends/localfs/localfs.go b/backends/localfs/localfs.go index b55c986..3f6f5ad 100644 --- a/backends/localfs/localfs.go +++ b/backends/localfs/localfs.go @@ -1,63 +1,149 @@ package localfs import ( - "errors" + "encoding/json" "io" "io/ioutil" - "net/http" "os" "path" + "time" "github.com/andreimarcu/linx-server/backends" + "github.com/andreimarcu/linx-server/helpers" ) type LocalfsBackend struct { - basePath string + metaPath string + filesPath string } -func (b LocalfsBackend) Delete(key string) error { - return os.Remove(path.Join(b.basePath, key)) +type MetadataJSON struct { + DeleteKey string `json:"delete_key"` + Sha256sum string `json:"sha256sum"` + Mimetype string `json:"mimetype"` + Size int64 `json:"size"` + Expiry int64 `json:"expiry"` + ArchiveFiles []string `json:"archive_files,omitempty"` +} + +func (b LocalfsBackend) Delete(key string) (err error) { + err = os.Remove(path.Join(b.filesPath, key)) + if err != nil { + return + } + err = os.Remove(path.Join(b.metaPath, key)) + return } func (b LocalfsBackend) Exists(key string) (bool, error) { - _, err := os.Stat(path.Join(b.basePath, key)) + _, err := os.Stat(path.Join(b.filesPath, key)) return err == nil, err } -func (b LocalfsBackend) Get(key string) ([]byte, error) { - return ioutil.ReadFile(path.Join(b.basePath, key)) +func (b LocalfsBackend) Head(key string) (metadata backends.Metadata, err error) { + f, err := os.Open(path.Join(b.metaPath, key)) + if os.IsNotExist(err) { + return metadata, backends.NotFoundErr + } else if err != nil { + return metadata, backends.BadMetadata + } + defer f.Close() + + decoder := json.NewDecoder(f) + + mjson := MetadataJSON{} + if err := decoder.Decode(&mjson); err != nil { + return metadata, backends.BadMetadata + } + + metadata.DeleteKey = mjson.DeleteKey + metadata.Mimetype = mjson.Mimetype + metadata.ArchiveFiles = mjson.ArchiveFiles + metadata.Sha256sum = mjson.Sha256sum + metadata.Expiry = time.Unix(mjson.Expiry, 0) + metadata.Size = mjson.Size + + return } -func (b LocalfsBackend) Put(key string, r io.Reader) (int64, error) { - dst, err := os.Create(path.Join(b.basePath, key)) +func (b LocalfsBackend) Get(key string) (metadata backends.Metadata, f io.ReadCloser, err error) { + metadata, err = b.Head(key) if err != nil { - return 0, err + return + } + + f, err = os.Open(path.Join(b.filesPath, key)) + if err != nil { + return + } + + return +} + +func (b LocalfsBackend) writeMetadata(key string, metadata backends.Metadata) error { + metaPath := path.Join(b.metaPath, key) + + mjson := MetadataJSON{ + DeleteKey: metadata.DeleteKey, + Mimetype: metadata.Mimetype, + ArchiveFiles: metadata.ArchiveFiles, + Sha256sum: metadata.Sha256sum, + Expiry: metadata.Expiry.Unix(), + Size: metadata.Size, + } + + dst, err := os.Create(metaPath) + if err != nil { + return err + } + defer dst.Close() + + encoder := json.NewEncoder(dst) + err = encoder.Encode(mjson) + if err != nil { + os.Remove(metaPath) + return err + } + + return nil +} + +func (b LocalfsBackend) Put(key string, r io.Reader, expiry time.Time, deleteKey string) (m backends.Metadata, err error) { + filePath := path.Join(b.filesPath, key) + + dst, err := os.Create(filePath) + if err != nil { + return } defer dst.Close() bytes, err := io.Copy(dst, r) if bytes == 0 { - b.Delete(key) - return bytes, errors.New("Empty file") + os.Remove(filePath) + return m, backends.FileEmptyError } else if err != nil { - b.Delete(key) - return bytes, err + os.Remove(filePath) + return m, err } - return bytes, err -} + m.Expiry = expiry + m.DeleteKey = deleteKey + m.Size = bytes + m.Mimetype, _ = helpers.DetectMime(dst) + m.Sha256sum, _ = helpers.Sha256sum(dst) + m.ArchiveFiles, _ = helpers.ListArchiveFiles(m.Mimetype, m.Size, dst) -func (b LocalfsBackend) Open(key string) (backends.ReadSeekCloser, error) { - return os.Open(path.Join(b.basePath, key)) -} + err = b.writeMetadata(key, m) + if err != nil { + os.Remove(filePath) + return + } -func (b LocalfsBackend) ServeFile(key string, w http.ResponseWriter, r *http.Request) { - filePath := path.Join(b.basePath, key) - http.ServeFile(w, r, filePath) + return } func (b LocalfsBackend) Size(key string) (int64, error) { - fileInfo, err := os.Stat(path.Join(b.basePath, key)) + fileInfo, err := os.Stat(path.Join(b.filesPath, key)) if err != nil { return 0, err } @@ -68,7 +154,7 @@ func (b LocalfsBackend) Size(key string) (int64, error) { func (b LocalfsBackend) List() ([]string, error) { var output []string - files, err := ioutil.ReadDir(b.basePath) + files, err := ioutil.ReadDir(b.filesPath) if err != nil { return nil, err } @@ -80,6 +166,9 @@ func (b LocalfsBackend) List() ([]string, error) { return output, nil } -func NewLocalfsBackend(basePath string) LocalfsBackend { - return LocalfsBackend{basePath: basePath} +func NewLocalfsBackend(metaPath string, filesPath string) LocalfsBackend { + return LocalfsBackend{ + metaPath: metaPath, + filesPath: filesPath, + } } diff --git a/backends/meta.go b/backends/meta.go index 27c3e41..7ba522d 100644 --- a/backends/meta.go +++ b/backends/meta.go @@ -5,11 +5,6 @@ import ( "time" ) -type MetaBackend interface { - Get(key string) (Metadata, error) - Put(key string, metadata *Metadata) error -} - type Metadata struct { DeleteKey string Sha256sum string diff --git a/backends/metajson/metajson.go b/backends/metajson/metajson.go deleted file mode 100644 index 8ec53c4..0000000 --- a/backends/metajson/metajson.go +++ /dev/null @@ -1,70 +0,0 @@ -package metajson - -import ( - "bytes" - "encoding/json" - "time" - - "github.com/andreimarcu/linx-server/backends" -) - -type MetadataJSON struct { - DeleteKey string `json:"delete_key"` - Sha256sum string `json:"sha256sum"` - Mimetype string `json:"mimetype"` - Size int64 `json:"size"` - Expiry int64 `json:"expiry"` - ArchiveFiles []string `json:"archive_files,omitempty"` -} - -type MetaJSONBackend struct { - storage backends.MetaStorageBackend -} - -func (m MetaJSONBackend) Put(key string, metadata *backends.Metadata) error { - mjson := MetadataJSON{} - mjson.DeleteKey = metadata.DeleteKey - mjson.Mimetype = metadata.Mimetype - mjson.ArchiveFiles = metadata.ArchiveFiles - mjson.Sha256sum = metadata.Sha256sum - mjson.Expiry = metadata.Expiry.Unix() - mjson.Size = metadata.Size - - byt, err := json.Marshal(mjson) - if err != nil { - return err - } - - if _, err := m.storage.Put(key, bytes.NewBuffer(byt)); err != nil { - return err - } - - return nil -} - -func (m MetaJSONBackend) Get(key string) (metadata backends.Metadata, err error) { - b, err := m.storage.Get(key) - if err != nil { - return metadata, backends.BadMetadata - } - - mjson := MetadataJSON{} - - err = json.Unmarshal(b, &mjson) - if err != nil { - return metadata, backends.BadMetadata - } - - metadata.DeleteKey = mjson.DeleteKey - metadata.Mimetype = mjson.Mimetype - metadata.ArchiveFiles = mjson.ArchiveFiles - metadata.Sha256sum = mjson.Sha256sum - metadata.Expiry = time.Unix(mjson.Expiry, 0) - metadata.Size = mjson.Size - - return -} - -func NewMetaJSONBackend(storage backends.MetaStorageBackend) MetaJSONBackend { - return MetaJSONBackend{storage: storage} -} diff --git a/backends/s3/s3.go b/backends/s3/s3.go new file mode 100644 index 0000000..7ae326c --- /dev/null +++ b/backends/s3/s3.go @@ -0,0 +1,192 @@ +package s3 + +import ( + "io" + "io/ioutil" + "os" + "strconv" + "time" + + "github.com/andreimarcu/linx-server/backends" + "github.com/andreimarcu/linx-server/helpers" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" +) + +type S3Backend struct { + bucket string + svc *s3.S3 +} + +func (b S3Backend) Delete(key string) error { + _, err := b.svc.DeleteObject(&s3.DeleteObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + }) + if err != nil { + return err + } + return nil +} + +func (b S3Backend) Exists(key string) (bool, error) { + _, err := b.svc.HeadObject(&s3.HeadObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + }) + return err == nil, err +} + +func (b S3Backend) Head(key string) (metadata backends.Metadata, err error) { + var result *s3.HeadObjectOutput + result, err = b.svc.HeadObject(&s3.HeadObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + if aerr.Code() == s3.ErrCodeNoSuchKey || aerr.Code() == "NotFound" { + err = backends.NotFoundErr + } + } + return + } + + metadata, err = unmapMetadata(result.Metadata) + return +} + +func (b S3Backend) Get(key string) (metadata backends.Metadata, r io.ReadCloser, err error) { + var result *s3.GetObjectOutput + result, err = b.svc.GetObject(&s3.GetObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + if aerr.Code() == s3.ErrCodeNoSuchKey || aerr.Code() == "NotFound" { + err = backends.NotFoundErr + } + } + return + } + + metadata, err = unmapMetadata(result.Metadata) + r = result.Body + return +} + +func mapMetadata(m backends.Metadata) map[string]*string { + return map[string]*string{ + "Expiry": aws.String(strconv.FormatInt(m.Expiry.Unix(), 10)), + "Delete_key": aws.String(m.DeleteKey), + "Size": aws.String(strconv.FormatInt(m.Size, 10)), + "Mimetype": aws.String(m.Mimetype), + "Sha256sum": aws.String(m.Sha256sum), + } +} + +func unmapMetadata(input map[string]*string) (m backends.Metadata, err error) { + expiry, err := strconv.ParseInt(aws.StringValue(input["Expiry"]), 10, 64) + if err != nil { + return m, err + } + m.Expiry = time.Unix(expiry, 0) + + m.Size, err = strconv.ParseInt(aws.StringValue(input["Size"]), 10, 64) + if err != nil { + return + } + + m.DeleteKey = aws.StringValue(input["Delete_key"]) + m.Mimetype = aws.StringValue(input["Mimetype"]) + m.Sha256sum = aws.StringValue(input["Sha256sum"]) + return +} + +func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey string) (m backends.Metadata, err error) { + tmpDst, err := ioutil.TempFile("", "linx-server-upload") + if err != nil { + return m, err + } + defer tmpDst.Close() + defer os.Remove(tmpDst.Name()) + + bytes, err := io.Copy(tmpDst, r) + if bytes == 0 { + return m, backends.FileEmptyError + } else if err != nil { + return m, err + } + + m.Expiry = expiry + m.DeleteKey = deleteKey + m.Size = bytes + m.Mimetype, _ = helpers.DetectMime(tmpDst) + m.Sha256sum, _ = helpers.Sha256sum(tmpDst) + // XXX: we may not be able to write this to AWS easily + //m.ArchiveFiles, _ = helpers.ListArchiveFiles(m.Mimetype, m.Size, tmpDst) + + uploader := s3manager.NewUploaderWithClient(b.svc) + input := &s3manager.UploadInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + Body: tmpDst, + Metadata: mapMetadata(m), + } + _, err = uploader.Upload(input) + if err != nil { + return + } + + return +} + +func (b S3Backend) Size(key string) (int64, error) { + input := &s3.HeadObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + } + result, err := b.svc.HeadObject(input) + if err != nil { + return 0, err + } + + return *result.ContentLength, nil +} + +func (b S3Backend) List() ([]string, error) { + var output []string + input := &s3.ListObjectsInput{ + Bucket: aws.String(b.bucket), + } + + results, err := b.svc.ListObjects(input) + if err != nil { + return nil, err + } + + + for _, object := range results.Contents { + output = append(output, *object.Key) + } + + return output, nil +} + +func NewS3Backend(bucket string, region string, endpoint string) S3Backend { + awsConfig := &aws.Config{} + if region != "" { + awsConfig.Region = aws.String(region) + } + if endpoint != "" { + awsConfig.Endpoint = aws.String(endpoint) + } + + sess := session.Must(session.NewSession(awsConfig)) + svc := s3.New(sess) + return S3Backend{bucket: bucket, svc: svc} +} diff --git a/backends/storage.go b/backends/storage.go index 2b51a2c..d40a2b9 100644 --- a/backends/storage.go +++ b/backends/storage.go @@ -1,24 +1,17 @@ package backends import ( + "errors" "io" - "net/http" + "time" ) -type ReadSeekCloser interface { - io.Reader - io.Closer - io.Seeker - io.ReaderAt -} - type StorageBackend interface { Delete(key string) error Exists(key string) (bool, error) - Get(key string) ([]byte, error) - Put(key string, r io.Reader) (int64, error) - Open(key string) (ReadSeekCloser, error) - ServeFile(key string, w http.ResponseWriter, r *http.Request) + Head(key string) (Metadata, error) + Get(key string) (Metadata, io.ReadCloser, error) + Put(key string, r io.Reader, expiry time.Time, deleteKey string) (Metadata, error) Size(key string) (int64, error) } @@ -26,3 +19,6 @@ type MetaStorageBackend interface { StorageBackend List() ([]string, error) } + +var NotFoundErr = errors.New("File not found.") +var FileEmptyError = errors.New("Empty file") diff --git a/delete.go b/delete.go index 61c6fa8..38e36e3 100644 --- a/delete.go +++ b/delete.go @@ -3,8 +3,8 @@ package main import ( "fmt" "net/http" - "os" + "github.com/andreimarcu/linx-server/backends" "github.com/zenazn/goji/web" ) @@ -13,24 +13,19 @@ func deleteHandler(c web.C, w http.ResponseWriter, r *http.Request) { filename := c.URLParams["name"] - // Ensure requested file actually exists - if _, readErr := fileBackend.Exists(filename); os.IsNotExist(readErr) { + // Ensure that file exists and delete key is correct + metadata, err := storageBackend.Head(filename) + if err == backends.NotFoundErr { notFoundHandler(c, w, r) // 404 - file doesn't exist return - } - - // Ensure delete key is correct - metadata, err := metadataRead(filename) - if err != nil { + } else if err != nil { unauthorizedHandler(c, w, r) // 401 - no metadata available return } if metadata.DeleteKey == requestKey { - fileDelErr := fileBackend.Delete(filename) - metaDelErr := metaStorageBackend.Delete(filename) - - if (fileDelErr != nil) || (metaDelErr != nil) { + err := storageBackend.Delete(filename) + if err != nil { oopsHandler(c, w, r, RespPLAIN, "Could not delete") return } diff --git a/display.go b/display.go index 63ba765..7258904 100644 --- a/display.go +++ b/display.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "io/ioutil" "net/http" "path/filepath" "regexp" @@ -9,6 +10,7 @@ import ( "strings" "time" + "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/expiry" "github.com/dustin/go-humanize" "github.com/flosch/pongo2" @@ -29,14 +31,11 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - _, err := checkFile(fileName) - if err == NotFoundErr { + metadata, err := checkFile(fileName) + if err == backends.NotFoundErr { notFoundHandler(c, w, r) return - } - - metadata, err := metadataRead(fileName) - if err != nil { + } else if err != nil { oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") return } @@ -78,8 +77,13 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { tpl = Templates["display/pdf.html"] } else if extension == "story" { + metadata, reader, err := storageBackend.Get(fileName) + if err != nil { + oopsHandler(c, w, r, RespHTML, err.Error()) + } + if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := fileBackend.Get(fileName) + bytes, err := ioutil.ReadAll(reader) if err == nil { extra["contents"] = string(bytes) lines = strings.Split(extra["contents"], "\n") @@ -88,8 +92,13 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } } else if extension == "md" { + metadata, reader, err := storageBackend.Get(fileName) + if err != nil { + oopsHandler(c, w, r, RespHTML, err.Error()) + } + if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := fileBackend.Get(fileName) + bytes, err := ioutil.ReadAll(reader) if err == nil { unsafe := blackfriday.MarkdownCommon(bytes) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) @@ -100,8 +109,13 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } } else if strings.HasPrefix(metadata.Mimetype, "text/") || supportedBinExtension(extension) { + metadata, reader, err := storageBackend.Get(fileName) + if err != nil { + oopsHandler(c, w, r, RespHTML, err.Error()) + } + if metadata.Size < maxDisplayFileSizeBytes { - bytes, err := fileBackend.Get(fileName) + bytes, err := ioutil.ReadAll(reader) if err == nil { extra["extension"] = extension extra["lang_hl"], extra["lang_ace"] = extensionToHlAndAceLangs(extension) @@ -117,14 +131,14 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } err = renderTemplate(tpl, pongo2.Context{ - "mime": metadata.Mimetype, - "filename": fileName, - "size": sizeHuman, - "expiry": expiryHuman, + "mime": metadata.Mimetype, + "filename": fileName, + "size": sizeHuman, + "expiry": expiryHuman, "expirylist": listExpirationTimes(), - "extra": extra, - "lines": lines, - "files": metadata.ArchiveFiles, + "extra": extra, + "lines": lines, + "files": metadata.ArchiveFiles, }, r, w) if err != nil { diff --git a/expiry.go b/expiry.go index 6d8887d..63b7757 100644 --- a/expiry.go +++ b/expiry.go @@ -24,7 +24,7 @@ type ExpirationTime struct { // Determine if the given filename is expired func isFileExpired(filename string) (bool, error) { - metadata, err := metadataRead(filename) + metadata, err := storageBackend.Head(filename) if err != nil { return false, err } diff --git a/fileserve.go b/fileserve.go index 3b20c4c..a3a249e 100644 --- a/fileserve.go +++ b/fileserve.go @@ -1,8 +1,10 @@ package main import ( + "io" "net/http" "net/url" + "strconv" "strings" "github.com/andreimarcu/linx-server/backends" @@ -14,14 +16,11 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] metadata, err := checkFile(fileName) - if err == NotFoundErr { + if err == backends.NotFoundErr { notFoundHandler(c, w, r) return - } else if err == backends.BadMetadata { - oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") - return } else if err != nil { - oopsHandler(c, w, r, RespAUTO, err.Error()) + oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") return } @@ -38,10 +37,23 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Security-Policy", Config.fileContentSecurityPolicy) w.Header().Set("Referrer-Policy", Config.fileReferrerPolicy) + _, reader, err := storageBackend.Get(fileName) + if err != nil { + oopsHandler(c, w, r, RespAUTO, err.Error()) + } + + w.Header().Set("Content-Type", metadata.Mimetype) + w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10)) w.Header().Set("Etag", metadata.Sha256sum) w.Header().Set("Cache-Control", "max-age=0") - fileBackend.ServeFile(fileName, w, r) + if r.Method != "HEAD" { + defer reader.Close() + + if _, err = io.CopyN(w, reader, metadata.Size); err != nil { + oopsHandler(c, w, r, RespAUTO, err.Error()) + } + } } func staticHandler(c web.C, w http.ResponseWriter, r *http.Request) { @@ -69,21 +81,14 @@ func staticHandler(c web.C, w http.ResponseWriter, r *http.Request) { } func checkFile(filename string) (metadata backends.Metadata, err error) { - _, err = fileBackend.Exists(filename) - if err != nil { - err = NotFoundErr - return - } - - metadata, err = metadataRead(filename) + metadata, err = storageBackend.Head(filename) if err != nil { return } if expiry.IsTsExpired(metadata.Expiry) { - fileBackend.Delete(filename) - metaStorageBackend.Delete(filename) - err = NotFoundErr + storageBackend.Delete(filename) + err = backends.NotFoundErr return } diff --git a/helpers/archive.go b/helpers/archive.go new file mode 100644 index 0000000..2a4380b --- /dev/null +++ b/helpers/archive.go @@ -0,0 +1,70 @@ +package helpers + +import ( + "archive/tar" + "archive/zip" + "compress/bzip2" + "compress/gzip" + "io" + "sort" +) + +type ReadSeekerAt interface { + io.Reader + io.Seeker + io.ReaderAt +} + +func ListArchiveFiles(mimetype string, size int64, r ReadSeekerAt) (files []string, err error) { + if mimetype == "application/x-tar" { + tReadr := tar.NewReader(r) + for { + hdr, err := tReadr.Next() + if err == io.EOF || err != nil { + break + } + if hdr.Typeflag == tar.TypeDir || hdr.Typeflag == tar.TypeReg { + files = append(files, hdr.Name) + } + } + sort.Strings(files) + } else if mimetype == "application/x-gzip" { + gzf, err := gzip.NewReader(r) + if err == nil { + tReadr := tar.NewReader(gzf) + for { + hdr, err := tReadr.Next() + if err == io.EOF || err != nil { + break + } + if hdr.Typeflag == tar.TypeDir || hdr.Typeflag == tar.TypeReg { + files = append(files, hdr.Name) + } + } + sort.Strings(files) + } + } else if mimetype == "application/x-bzip" { + bzf := bzip2.NewReader(r) + tReadr := tar.NewReader(bzf) + for { + hdr, err := tReadr.Next() + if err == io.EOF || err != nil { + break + } + if hdr.Typeflag == tar.TypeDir || hdr.Typeflag == tar.TypeReg { + files = append(files, hdr.Name) + } + } + sort.Strings(files) + } else if mimetype == "application/zip" { + zf, err := zip.NewReader(r, size) + if err == nil { + for _, f := range zf.File { + files = append(files, f.Name) + } + } + sort.Strings(files) + } + + return +} diff --git a/helpers/helpers.go b/helpers/helpers.go new file mode 100644 index 0000000..aef68ff --- /dev/null +++ b/helpers/helpers.go @@ -0,0 +1,67 @@ +package helpers + +import ( + "encoding/hex" + "io" + "unicode" + + "github.com/minio/sha256-simd" + "gopkg.in/h2non/filetype.v1" +) + +func DetectMime(r io.ReadSeeker) (string, error) { + // Get first 512 bytes for mimetype detection + header := make([]byte, 512) + + r.Seek(0, 0) + r.Read(header) + r.Seek(0, 0) + + kind, err := filetype.Match(header) + if err != nil { + return "application/octet-stream", err + } else if kind.MIME.Value != "" { + return kind.MIME.Value, nil + } + + // Check if the file seems anything like text + if printable(header) { + return "text/plain", nil + } else { + return "application/octet-stream", nil + } +} + +func Sha256sum(r io.ReadSeeker) (string, error) { + hasher := sha256.New() + + r.Seek(0, 0) + _, err := io.Copy(hasher, r) + if err != nil { + return "", err + } + + r.Seek(0, 0) + + return hex.EncodeToString(hasher.Sum(nil)), nil +} + +func printable(data []byte) bool { + for i, b := range data { + r := rune(b) + + // A null terminator that's not at the beginning of the file + if r == 0 && i == 0 { + return false + } else if r == 0 && i < 0 { + continue + } + + if r > unicode.MaxASCII { + return false + } + + } + + return true +} diff --git a/linx-cleanup/cleanup.go b/linx-cleanup/cleanup.go index 9ea89ae..88c2bce 100644 --- a/linx-cleanup/cleanup.go +++ b/linx-cleanup/cleanup.go @@ -5,7 +5,6 @@ import ( "log" "github.com/andreimarcu/linx-server/backends/localfs" - "github.com/andreimarcu/linx-server/backends/metajson" "github.com/andreimarcu/linx-server/expiry" ) @@ -22,17 +21,15 @@ func main() { "don't log deleted files") flag.Parse() - metaStorageBackend := localfs.NewLocalfsBackend(metaDir) - metaBackend := metajson.NewMetaJSONBackend(metaStorageBackend) - fileBackend := localfs.NewLocalfsBackend(filesDir) + fileBackend := localfs.NewLocalfsBackend(metaDir, filesDir) - files, err := metaStorageBackend.List() + files, err := fileBackend.List() if err != nil { panic(err) } for _, filename := range files { - metadata, err := metaBackend.Get(filename) + metadata, err := fileBackend.Head(filename) if err != nil { if !noLogs { log.Printf("Failed to find metadata for %s", filename) @@ -44,7 +41,6 @@ func main() { log.Printf("Delete %s", filename) } fileBackend.Delete(filename) - metaStorageBackend.Delete(filename) } } } diff --git a/meta.go b/meta.go deleted file mode 100644 index 2fc7b81..0000000 --- a/meta.go +++ /dev/null @@ -1,165 +0,0 @@ -package main - -import ( - "archive/tar" - "archive/zip" - "compress/bzip2" - "compress/gzip" - "encoding/hex" - "errors" - "io" - "sort" - "time" - "unicode" - - "github.com/andreimarcu/linx-server/backends" - "github.com/andreimarcu/linx-server/expiry" - "github.com/dchest/uniuri" - "github.com/minio/sha256-simd" - "gopkg.in/h2non/filetype.v1" -) - -var NotFoundErr = errors.New("File not found.") - -func generateMetadata(fName string, exp time.Time, delKey string) (m backends.Metadata, err error) { - file, err := fileBackend.Open(fName) - if err != nil { - return - } - defer file.Close() - - m.Size, err = fileBackend.Size(fName) - if err != nil { - return - } - - m.Expiry = exp - - if delKey == "" { - m.DeleteKey = uniuri.NewLen(30) - } else { - m.DeleteKey = delKey - } - - // Get first 512 bytes for mimetype detection - header := make([]byte, 512) - file.Read(header) - - kind, err := filetype.Match(header) - if err != nil { - m.Mimetype = "application/octet-stream" - } else { - m.Mimetype = kind.MIME.Value - } - - if m.Mimetype == "" { - // Check if the file seems anything like text - if printable(header) { - m.Mimetype = "text/plain" - } else { - m.Mimetype = "application/octet-stream" - } - } - - // Compute the sha256sum - hasher := sha256.New() - file.Seek(0, 0) - _, err = io.Copy(hasher, file) - if err == nil { - m.Sha256sum = hex.EncodeToString(hasher.Sum(nil)) - } - file.Seek(0, 0) - - // If archive, grab list of filenames - if m.Mimetype == "application/x-tar" { - tReadr := tar.NewReader(file) - for { - hdr, err := tReadr.Next() - if err == io.EOF || err != nil { - break - } - if hdr.Typeflag == tar.TypeDir || hdr.Typeflag == tar.TypeReg { - m.ArchiveFiles = append(m.ArchiveFiles, hdr.Name) - } - } - sort.Strings(m.ArchiveFiles) - } else if m.Mimetype == "application/x-gzip" { - gzf, err := gzip.NewReader(file) - if err == nil { - tReadr := tar.NewReader(gzf) - for { - hdr, err := tReadr.Next() - if err == io.EOF || err != nil { - break - } - if hdr.Typeflag == tar.TypeDir || hdr.Typeflag == tar.TypeReg { - m.ArchiveFiles = append(m.ArchiveFiles, hdr.Name) - } - } - sort.Strings(m.ArchiveFiles) - } - } else if m.Mimetype == "application/x-bzip" { - bzf := bzip2.NewReader(file) - tReadr := tar.NewReader(bzf) - for { - hdr, err := tReadr.Next() - if err == io.EOF || err != nil { - break - } - if hdr.Typeflag == tar.TypeDir || hdr.Typeflag == tar.TypeReg { - m.ArchiveFiles = append(m.ArchiveFiles, hdr.Name) - } - } - sort.Strings(m.ArchiveFiles) - } else if m.Mimetype == "application/zip" { - zf, err := zip.NewReader(file, m.Size) - if err == nil { - for _, f := range zf.File { - m.ArchiveFiles = append(m.ArchiveFiles, f.Name) - } - } - sort.Strings(m.ArchiveFiles) - } - - return -} - -func metadataWrite(filename string, metadata *backends.Metadata) error { - return metaBackend.Put(filename, metadata) -} - -func metadataRead(filename string) (metadata backends.Metadata, err error) { - metadata, err = metaBackend.Get(filename) - if err != nil { - // Metadata does not exist, generate one - newMData, err := generateMetadata(filename, expiry.NeverExpire, "") - if err != nil { - return metadata, err - } - metadataWrite(filename, &newMData) - - metadata, err = metaBackend.Get(filename) - } - - return -} - -func printable(data []byte) bool { - for i, b := range data { - r := rune(b) - - // A null terminator that's not at the beginning of the file - if r == 0 && i == 0 { - return false - } else if r == 0 && i < 0 { - continue - } - - if r > unicode.MaxASCII { - return false - } - - } - - return true -} diff --git a/pages.go b/pages.go index f58fa88..bb38f37 100644 --- a/pages.go +++ b/pages.go @@ -64,12 +64,10 @@ func oopsHandler(c web.C, w http.ResponseWriter, r *http.Request, rt RespType, m w.WriteHeader(500) renderTemplate(Templates["oops.html"], pongo2.Context{"msg": msg}, r, w) return - } else if rt == RespPLAIN { w.WriteHeader(500) fmt.Fprintf(w, "%s", msg) return - } else if rt == RespJSON { js, _ := json.Marshal(map[string]string{ "error": msg, @@ -79,7 +77,6 @@ func oopsHandler(c web.C, w http.ResponseWriter, r *http.Request, rt RespType, m w.WriteHeader(500) w.Write(js) return - } else if rt == RespAUTO { if strings.EqualFold("application/json", r.Header.Get("Accept")) { oopsHandler(c, w, r, RespJSON, msg) @@ -89,11 +86,33 @@ func oopsHandler(c web.C, w http.ResponseWriter, r *http.Request, rt RespType, m } } -func badRequestHandler(c web.C, w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - err := renderTemplate(Templates["400.html"], pongo2.Context{}, r, w) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) +func badRequestHandler(c web.C, w http.ResponseWriter, r *http.Request, rt RespType, msg string) { + if rt == RespHTML { + w.WriteHeader(http.StatusBadRequest) + err := renderTemplate(Templates["400.html"], pongo2.Context{"msg": msg}, r, w) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } else if rt == RespPLAIN { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "%s", msg) + return + } else if rt == RespJSON { + js, _ := json.Marshal(map[string]string{ + "error": msg, + }) + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusBadRequest) + w.Write(js) + return + } else if rt == RespAUTO { + if strings.EqualFold("application/json", r.Header.Get("Accept")) { + badRequestHandler(c, w, r, RespJSON, msg) + } else { + badRequestHandler(c, w, r, RespHTML, msg) + } } } diff --git a/server.go b/server.go index c7b4342..851a7cf 100644 --- a/server.go +++ b/server.go @@ -16,7 +16,7 @@ import ( "github.com/GeertJohan/go.rice" "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/backends/localfs" - "github.com/andreimarcu/linx-server/backends/metajson" + "github.com/andreimarcu/linx-server/backends/s3" "github.com/flosch/pongo2" "github.com/vharitonsky/iniflags" "github.com/zenazn/goji/graceful" @@ -61,6 +61,9 @@ var Config struct { remoteAuthFile string addHeaders headerList noDirectAgents bool + s3Endpoint string + s3Region string + s3Bucket string } var Templates = make(map[string]*pongo2.Template) @@ -70,8 +73,7 @@ var timeStarted time.Time var timeStartedStr string var remoteAuthKeys []string var metaStorageBackend backends.MetaStorageBackend -var metaBackend backends.MetaBackend -var fileBackend backends.StorageBackend +var storageBackend backends.StorageBackend func setup() *web.Mux { mux := web.New() @@ -135,9 +137,11 @@ func setup() *web.Mux { Config.selifPath = Config.selifPath + "/" } - metaStorageBackend = localfs.NewLocalfsBackend(Config.metaDir) - metaBackend = metajson.NewMetaJSONBackend(metaStorageBackend) - fileBackend = localfs.NewLocalfsBackend(Config.filesDir) + if Config.s3Bucket != "" { + storageBackend = s3.NewS3Backend(Config.s3Bucket, Config.s3Region, Config.s3Endpoint) + } else { + storageBackend = localfs.NewLocalfsBackend(Config.metaDir, Config.filesDir) + } // Template setup p2l, err := NewPongo2TemplatesLoader() @@ -255,6 +259,12 @@ func main() { "Add an arbitrary header to the response. This option can be used multiple times.") flag.BoolVar(&Config.noDirectAgents, "nodirectagents", false, "disable serving files directly for wget/curl user agents") + flag.StringVar(&Config.s3Endpoint, "s3-endpoint", "", + "S3 endpoint") + flag.StringVar(&Config.s3Region, "s3-region", "", + "S3 region") + flag.StringVar(&Config.s3Bucket, "s3-bucket", "", + "S3 bucket to use for files and metadata") iniflags.Parse() diff --git a/server_test.go b/server_test.go index d9374ab..a1ec853 100644 --- a/server_test.go +++ b/server_test.go @@ -486,7 +486,6 @@ func TestPostJSONUploadMaxExpiry(t *testing.T) { var myjson RespOkJSON err = json.Unmarshal([]byte(w.Body.String()), &myjson) if err != nil { - fmt.Println(w.Body.String()) t.Fatal(err) } @@ -643,14 +642,45 @@ func TestPostEmptyUpload(t *testing.T) { mux.ServeHTTP(w, req) - if w.Code != 500 { + if w.Code != 400 { t.Log(w.Body.String()) - t.Fatalf("Status code is not 500, but %d", w.Code) + t.Fatalf("Status code is not 400, but %d", w.Code) + } +} + +func TestPostTooLargeUpload(t *testing.T) { + mux := setup() + oldMaxSize := Config.maxSize + Config.maxSize = 2 + w := httptest.NewRecorder() + + filename := generateBarename() + ".txt" + + var b bytes.Buffer + mw := multipart.NewWriter(&b) + fw, err := mw.CreateFormFile("file", filename) + if err != nil { + t.Fatal(err) } - if !strings.Contains(w.Body.String(), "Empty file") { - t.Fatal("Response did not contain 'Empty file'") + fw.Write([]byte("test content")) + mw.Close() + + req, err := http.NewRequest("POST", "/upload/", &b) + req.Header.Set("Content-Type", mw.FormDataContentType()) + req.Header.Set("Referer", Config.siteURL) + if err != nil { + t.Fatal(err) } + + mux.ServeHTTP(w, req) + + if w.Code != 400 { + t.Log(w.Body.String()) + t.Fatalf("Status code is not 400, but %d", w.Code) + } + + Config.maxSize = oldMaxSize } func TestPostEmptyJSONUpload(t *testing.T) { @@ -679,9 +709,9 @@ func TestPostEmptyJSONUpload(t *testing.T) { mux.ServeHTTP(w, req) - if w.Code != 500 { + if w.Code != 400 { t.Log(w.Body.String()) - t.Fatalf("Status code is not 500, but %d", w.Code) + t.Fatalf("Status code is not 400, but %d", w.Code) } var myjson RespErrJSON @@ -690,7 +720,7 @@ func TestPostEmptyJSONUpload(t *testing.T) { t.Fatal(err) } - if myjson.Error != "Could not upload file: Empty file" { + if myjson.Error != "Empty file" { t.Fatal("Json 'error' was not 'Empty file' but " + myjson.Error) } } @@ -768,11 +798,41 @@ func TestPutEmptyUpload(t *testing.T) { mux.ServeHTTP(w, req) - if !strings.Contains(w.Body.String(), "Empty file") { - t.Fatal("Response doesn't contain'Empty file'") + if w.Code != 400 { + t.Fatalf("Status code is not 400, but %d", w.Code) } } +func TestPutTooLargeUpload(t *testing.T) { + mux := setup() + oldMaxSize := Config.maxSize + Config.maxSize = 2 + + w := httptest.NewRecorder() + + filename := generateBarename() + ".file" + + req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File too big")) + if err != nil { + t.Fatal(err) + } + + req.Header.Set("Linx-Randomize", "yes") + + mux.ServeHTTP(w, req) + + if w.Code != 500 { + t.Log(w.Body.String()) + t.Fatalf("Status code is not 500, but %d", w.Code) + } + + if !strings.Contains(w.Body.String(), "request body too large") { + t.Fatal("Response did not contain 'request body too large'") + } + + Config.maxSize = oldMaxSize +} + func TestPutJSONUpload(t *testing.T) { var myjson RespOkJSON diff --git a/torrent.go b/torrent.go index 4155872..c5e7a58 100644 --- a/torrent.go +++ b/torrent.go @@ -2,65 +2,44 @@ package main import ( "bytes" - "crypto/sha1" "fmt" "io" "net/http" "time" "github.com/andreimarcu/linx-server/backends" + "github.com/andreimarcu/linx-server/expiry" + "github.com/andreimarcu/linx-server/torrent" "github.com/zeebo/bencode" "github.com/zenazn/goji/web" ) -const ( - TORRENT_PIECE_LENGTH = 262144 -) +func createTorrent(fileName string, f io.Reader, r *http.Request) ([]byte, error) { + url := getSiteURL(r) + Config.selifPath + fileName + chunk := make([]byte, torrent.TORRENT_PIECE_LENGTH) -type TorrentInfo struct { - PieceLength int `bencode:"piece length"` - Pieces string `bencode:"pieces"` - Name string `bencode:"name"` - Length int `bencode:"length"` -} - -type Torrent struct { - Encoding string `bencode:"encoding"` - Info TorrentInfo `bencode:"info"` - UrlList []string `bencode:"url-list"` -} - -func hashPiece(piece []byte) []byte { - h := sha1.New() - h.Write(piece) - return h.Sum(nil) -} - -func createTorrent(fileName string, f io.ReadCloser, r *http.Request) ([]byte, error) { - chunk := make([]byte, TORRENT_PIECE_LENGTH) - - torrent := Torrent{ + t := torrent.Torrent{ Encoding: "UTF-8", - Info: TorrentInfo{ - PieceLength: TORRENT_PIECE_LENGTH, + Info: torrent.TorrentInfo{ + PieceLength: torrent.TORRENT_PIECE_LENGTH, Name: fileName, }, - UrlList: []string{fmt.Sprintf("%s%s%s", getSiteURL(r), Config.selifPath, fileName)}, + UrlList: []string{url}, } for { - n, err := f.Read(chunk) + n, err := io.ReadFull(f, chunk) if err == io.EOF { break - } else if err != nil { + } else if err != nil && err != io.ErrUnexpectedEOF { return []byte{}, err } - torrent.Info.Length += n - torrent.Info.Pieces += string(hashPiece(chunk[:n])) + t.Info.Length += n + t.Info.Pieces += string(torrent.HashPiece(chunk[:n])) } - data, err := bencode.EncodeBytes(&torrent) + data, err := bencode.EncodeBytes(&t) if err != nil { return []byte{}, err } @@ -71,22 +50,25 @@ func createTorrent(fileName string, f io.ReadCloser, r *http.Request) ([]byte, e func fileTorrentHandler(c web.C, w http.ResponseWriter, r *http.Request) { fileName := c.URLParams["name"] - _, err := checkFile(fileName) - if err == NotFoundErr { + metadata, f, err := storageBackend.Get(fileName) + if err == backends.NotFoundErr { notFoundHandler(c, w, r) return } else if err == backends.BadMetadata { oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") return - } - - f, err := fileBackend.Open(fileName) - if err != nil { - oopsHandler(c, w, r, RespHTML, "Could not create torrent.") + } else if err != nil { + oopsHandler(c, w, r, RespAUTO, err.Error()) return } defer f.Close() + if expiry.IsTsExpired(metadata.Expiry) { + storageBackend.Delete(fileName) + notFoundHandler(c, w, r) + return + } + encoded, err := createTorrent(fileName, f, r) if err != nil { oopsHandler(c, w, r, RespHTML, "Could not create torrent.") diff --git a/torrent/torrent.go b/torrent/torrent.go new file mode 100644 index 0000000..a47d884 --- /dev/null +++ b/torrent/torrent.go @@ -0,0 +1,28 @@ +package torrent + +import ( + "crypto/sha1" +) + +const ( + TORRENT_PIECE_LENGTH = 262144 +) + +type TorrentInfo struct { + PieceLength int `bencode:"piece length"` + Pieces string `bencode:"pieces"` + Name string `bencode:"name"` + Length int `bencode:"length"` +} + +type Torrent struct { + Encoding string `bencode:"encoding"` + Info TorrentInfo `bencode:"info"` + UrlList []string `bencode:"url-list"` +} + +func HashPiece(piece []byte) []byte { + h := sha1.New() + h.Write(piece) + return h.Sum(nil) +} diff --git a/torrent_test.go b/torrent_test.go index b553231..1d227fd 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -5,12 +5,13 @@ import ( "os" "testing" + "github.com/andreimarcu/linx-server/torrent" "github.com/zeebo/bencode" ) func TestCreateTorrent(t *testing.T) { fileName := "server.go" - var decoded Torrent + var decoded torrent.Torrent f, err := os.Open("server.go") if err != nil { @@ -52,7 +53,7 @@ func TestCreateTorrent(t *testing.T) { } func TestCreateTorrentWithImage(t *testing.T) { - var decoded Torrent + var decoded torrent.Torrent f, err := os.Open("static/images/404.jpg") if err != nil { diff --git a/upload.go b/upload.go index acdd204..d46c4d5 100644 --- a/upload.go +++ b/upload.go @@ -22,6 +22,7 @@ import ( "gopkg.in/h2non/filetype.v1" ) +var FileTooLargeError = errors.New("File too large.") var fileBlacklist = map[string]bool{ "favicon.ico": true, "index.htm": true, @@ -34,10 +35,11 @@ var fileBlacklist = map[string]bool{ // Describes metadata directly from the user request type UploadRequest struct { src io.Reader + size int64 filename string expiry time.Duration // Seconds until expiry, 0 = never + deleteKey string // Empty string if not defined randomBarename bool - deletionKey string // Empty string if not defined } // Metadata associated with a file as it would actually be stored @@ -48,7 +50,7 @@ type Upload struct { func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { if !strictReferrerCheck(r, getSiteURL(r), []string{"Linx-Delete-Key", "Linx-Expiry", "Linx-Randomize", "X-Requested-With"}) { - badRequestHandler(c, w, r) + badRequestHandler(c, w, r, RespAUTO, "") return } @@ -65,32 +67,39 @@ func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { } defer file.Close() - r.ParseForm() - if r.Form.Get("randomize") == "true" { - upReq.randomBarename = true - } - upReq.expiry = parseExpiry(r.Form.Get("expires")) upReq.src = file + upReq.size = headers.Size upReq.filename = headers.Filename } else { - if r.FormValue("content") == "" { - oopsHandler(c, w, r, RespHTML, "Empty file") + if r.PostFormValue("content") == "" { + badRequestHandler(c, w, r, RespAUTO, "Empty file") return } - extension := r.FormValue("extension") + extension := r.PostFormValue("extension") if extension == "" { extension = "txt" } - upReq.src = strings.NewReader(r.FormValue("content")) - upReq.expiry = parseExpiry(r.FormValue("expires")) - upReq.filename = r.FormValue("filename") + "." + extension + content := r.PostFormValue("content") + + upReq.src = strings.NewReader(content) + upReq.size = int64(len(content)) + upReq.filename = r.PostFormValue("filename") + "." + extension + } + + upReq.expiry = parseExpiry(r.PostFormValue("expires")) + + if r.PostFormValue("randomize") == "true" { + upReq.randomBarename = true } upload, err := processUpload(upReq) if strings.EqualFold("application/json", r.Header.Get("Accept")) { - if err != nil { + if err == FileTooLargeError || err == backends.FileEmptyError { + badRequestHandler(c, w, r, RespJSON, err.Error()) + return + } else if err != nil { oopsHandler(c, w, r, RespJSON, "Could not upload file: "+err.Error()) return } @@ -99,14 +108,16 @@ func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Write(js) } else { - if err != nil { + if err == FileTooLargeError || err == backends.FileEmptyError { + badRequestHandler(c, w, r, RespHTML, err.Error()) + return + } else if err != nil { oopsHandler(c, w, r, RespHTML, "Could not upload file: "+err.Error()) return } http.Redirect(w, r, Config.sitePath+upload.Filename, 303) } - } func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { @@ -115,12 +126,15 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { defer r.Body.Close() upReq.filename = c.URLParams["name"] - upReq.src = r.Body + upReq.src = http.MaxBytesReader(w, r.Body, Config.maxSize) upload, err := processUpload(upReq) if strings.EqualFold("application/json", r.Header.Get("Accept")) { - if err != nil { + if err == FileTooLargeError || err == backends.FileEmptyError { + badRequestHandler(c, w, r, RespJSON, err.Error()) + return + } else if err != nil { oopsHandler(c, w, r, RespJSON, "Could not upload file: "+err.Error()) return } @@ -129,7 +143,10 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Write(js) } else { - if err != nil { + if err == FileTooLargeError || err == backends.FileEmptyError { + badRequestHandler(c, w, r, RespPLAIN, err.Error()) + return + } else if err != nil { oopsHandler(c, w, r, RespPLAIN, "Could not upload file: "+err.Error()) return } @@ -162,8 +179,8 @@ func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) { } upReq.filename = filepath.Base(grabUrl.Path) - upReq.src = resp.Body - upReq.deletionKey = r.FormValue("deletekey") + upReq.src = http.MaxBytesReader(w, resp.Body, Config.maxSize) + upReq.deleteKey = r.FormValue("deletekey") upReq.randomBarename = r.FormValue("randomize") == "yes" upReq.expiry = parseExpiry(r.FormValue("expiry")) @@ -193,15 +210,18 @@ func uploadHeaderProcess(r *http.Request, upReq *UploadRequest) { upReq.randomBarename = true } - upReq.deletionKey = r.Header.Get("Linx-Delete-Key") + upReq.deleteKey = r.Header.Get("Linx-Delete-Key") // Get seconds until expiry. Non-integer responses never expire. expStr := r.Header.Get("Linx-Expiry") upReq.expiry = parseExpiry(expStr) - } func processUpload(upReq UploadRequest) (upload Upload, err error) { + if upReq.size > Config.maxSize { + return upload, FileTooLargeError + } + // Determine the appropriate filename, then write to disk barename, extension := barePlusExt(upReq.filename) @@ -215,7 +235,7 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { header = make([]byte, 512) n, _ := upReq.src.Read(header) if n == 0 { - return upload, errors.New("Empty file") + return upload, backends.FileEmptyError } header = header[:n] @@ -231,13 +251,13 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { upload.Filename = strings.Join([]string{barename, extension}, ".") upload.Filename = strings.Replace(upload.Filename, " ", "", -1) - fileexists, _ := fileBackend.Exists(upload.Filename) + fileexists, _ := storageBackend.Exists(upload.Filename) // Check if the delete key matches, in which case overwrite if fileexists { - metad, merr := metadataRead(upload.Filename) + metad, merr := storageBackend.Head(upload.Filename) if merr == nil { - if upReq.deletionKey == metad.DeleteKey { + if upReq.deleteKey == metad.DeleteKey { fileexists = false } } @@ -252,7 +272,7 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { } upload.Filename = strings.Join([]string{barename, extension}, ".") - fileexists, err = fileBackend.Exists(upload.Filename) + fileexists, err = storageBackend.Exists(upload.Filename) } if fileBlacklist[strings.ToLower(upload.Filename)] { @@ -267,24 +287,15 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { fileExpiry = time.Now().Add(upReq.expiry) } - bytes, err := fileBackend.Put(upload.Filename, io.MultiReader(bytes.NewReader(header), upReq.src)) - if err != nil { - return upload, err - } else if bytes > Config.maxSize { - fileBackend.Delete(upload.Filename) - return upload, errors.New("File too large") + if upReq.deleteKey == "" { + upReq.deleteKey = uniuri.NewLen(30) } - upload.Metadata, err = generateMetadata(upload.Filename, fileExpiry, upReq.deletionKey) + upload.Metadata, err = storageBackend.Put(upload.Filename, io.MultiReader(bytes.NewReader(header), upReq.src), fileExpiry, upReq.deleteKey) if err != nil { - fileBackend.Delete(upload.Filename) - return - } - err = metadataWrite(upload.Filename, &upload.Metadata) - if err != nil { - fileBackend.Delete(upload.Filename) - return + return upload, err } + return } From 35c4415f8d1e96843fb65a469533062738ccb39e Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 24 Jan 2019 23:39:17 -0800 Subject: [PATCH 057/233] Document storage backend usage --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8d4ef11..957757c 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,7 @@ allowhotlink = true - ```-bind 127.0.0.1:8080``` -- what to bind to (default is 127.0.0.1:8080) - ```-sitename myLinx``` -- the site name displayed on top (default is inferred from Host header) - ```-siteurl "http://mylinx.example.org/"``` -- the site url (default is inferred from execution context) -- ```-selifpath "selif"``` -- path relative to site base url (the "selif" in https://mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) -- ```-filespath files/``` -- Path to store uploads (default is files/) -- ```-metapath meta/``` -- Path to store information about uploads (default is meta/) +- ```-selifpath "selif"``` -- path relative to site base url (the "selif" in mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) - ```-maxexpiry 86400``` -- maximum expiration time in seconds (default is 0, which is no expiry) - ```-allowhotlink``` -- Allow file hotlinking @@ -56,6 +54,15 @@ allowhotlink = true - ```-remoteuploads``` -- (optionally) enable remote uploads (/upload?url=https://...) - ```-nologs``` -- (optionally) disable request logs in stdout +#### Storage backends +The following storage backends are available: + +|Name|Options|Notes +|----|-------|----- +|LocalFS|```-filespath files/``` -- Path to store uploads (default is files/)
```-metapath meta/``` -- Path to store information about uploads (default is meta/)|Enabled by default, this backend uses the filesystem| +|S3|```-s3-endpoint https://...``` -- S3 endpoint
```-s3-region us-east-1``` -- S3 region
```-s3-bucket mybucket``` -- S3 bucket to use for files and metadata

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).| + + #### SSL with built-in server - ```-certfile path/to/your.crt``` -- Path to the ssl certificate (required if you want to use the https server) - ```-keyfile path/to/your.key``` -- Path to the ssl key (required if you want to use the https server) From 207c19e3df34837489cee90ba4e2fa97f150f4a1 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Fri, 25 Jan 2019 08:10:06 +0000 Subject: [PATCH 058/233] Add -s3-force-path-style flag and config option (#157) This option forces path-style addressing instead of using a subdomain. This appears to be needed by Minio. --- README.md | 2 +- backends/s3/s3.go | 5 ++++- server.go | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 957757c..0ad244b 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ The following storage backends are available: |Name|Options|Notes |----|-------|----- |LocalFS|```-filespath files/``` -- Path to store uploads (default is files/)
```-metapath meta/``` -- Path to store information about uploads (default is meta/)|Enabled by default, this backend uses the filesystem| -|S3|```-s3-endpoint https://...``` -- S3 endpoint
```-s3-region us-east-1``` -- S3 region
```-s3-bucket mybucket``` -- S3 bucket to use for files and metadata

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).| +|S3|```-s3-endpoint https://...``` -- S3 endpoint
```-s3-region us-east-1``` -- S3 region
```-s3-bucket mybucket``` -- S3 bucket to use for files and metadata
```-s3-use-path-style``` -- force path-style addresing (e.g. https://s3.amazonaws.com/linx/example.txt)

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).| #### SSL with built-in server diff --git a/backends/s3/s3.go b/backends/s3/s3.go index 7ae326c..45067c1 100644 --- a/backends/s3/s3.go +++ b/backends/s3/s3.go @@ -177,7 +177,7 @@ func (b S3Backend) List() ([]string, error) { return output, nil } -func NewS3Backend(bucket string, region string, endpoint string) S3Backend { +func NewS3Backend(bucket string, region string, endpoint string, forcePathStyle bool) S3Backend { awsConfig := &aws.Config{} if region != "" { awsConfig.Region = aws.String(region) @@ -185,6 +185,9 @@ func NewS3Backend(bucket string, region string, endpoint string) S3Backend { if endpoint != "" { awsConfig.Endpoint = aws.String(endpoint) } + if forcePathStyle == true { + awsConfig.S3ForcePathStyle = aws.Bool(true) + } sess := session.Must(session.NewSession(awsConfig)) svc := s3.New(sess) diff --git a/server.go b/server.go index 851a7cf..7883a07 100644 --- a/server.go +++ b/server.go @@ -64,6 +64,7 @@ var Config struct { s3Endpoint string s3Region string s3Bucket string + s3ForcePathStyle bool } var Templates = make(map[string]*pongo2.Template) @@ -138,7 +139,7 @@ func setup() *web.Mux { } if Config.s3Bucket != "" { - storageBackend = s3.NewS3Backend(Config.s3Bucket, Config.s3Region, Config.s3Endpoint) + storageBackend = s3.NewS3Backend(Config.s3Bucket, Config.s3Region, Config.s3Endpoint, Config.s3ForcePathStyle) } else { storageBackend = localfs.NewLocalfsBackend(Config.metaDir, Config.filesDir) } @@ -265,6 +266,8 @@ func main() { "S3 region") flag.StringVar(&Config.s3Bucket, "s3-bucket", "", "S3 bucket to use for files and metadata") + flag.BoolVar(&Config.s3ForcePathStyle, "s3-force-path-style", false, + "Force path-style addressing for S3 (e.g. https://s3.amazonaws.com/linx/example.txt)") iniflags.Parse() From f6cd7fc6fe6f5d9dc6bc27cabb47671250222b5b Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Fri, 25 Jan 2019 00:20:52 -0800 Subject: [PATCH 059/233] Tweak documentation --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0ad244b..6cb85ab 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ allowhotlink = true #### Options - ```-bind 127.0.0.1:8080``` -- what to bind to (default is 127.0.0.1:8080) - ```-sitename myLinx``` -- the site name displayed on top (default is inferred from Host header) -- ```-siteurl "http://mylinx.example.org/"``` -- the site url (default is inferred from execution context) +- ```-siteurl "https://mylinx.example.org/"``` -- the site url (default is inferred from execution context) - ```-selifpath "selif"``` -- path relative to site base url (the "selif" in mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) - ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) - ```-maxexpiry 86400``` -- maximum expiration time in seconds (default is 0, which is no expiry) @@ -57,10 +57,10 @@ allowhotlink = true #### Storage backends The following storage backends are available: -|Name|Options|Notes -|----|-------|----- -|LocalFS|```-filespath files/``` -- Path to store uploads (default is files/)
```-metapath meta/``` -- Path to store information about uploads (default is meta/)|Enabled by default, this backend uses the filesystem| -|S3|```-s3-endpoint https://...``` -- S3 endpoint
```-s3-region us-east-1``` -- S3 region
```-s3-bucket mybucket``` -- S3 bucket to use for files and metadata
```-s3-use-path-style``` -- force path-style addresing (e.g. https://s3.amazonaws.com/linx/example.txt)

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).| +|Name|Notes|Options +|----|-----|------- +|LocalFS|Enabled by default, this backend uses the filesystem|```-filespath files/``` -- Path to store uploads (default is files/)
```-metapath meta/``` -- Path to store information about uploads (default is meta/)| +|S3|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).|```-s3-endpoint https://...``` -- S3 endpoint
```-s3-region us-east-1``` -- S3 region
```-s3-bucket mybucket``` -- S3 bucket to use for files and metadata
```-s3-force-path-style``` (optional) -- force path-style addresing (e.g. https://s3.amazonaws.com/linx/example.txt)

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token| #### SSL with built-in server From d5aa09a65c1949dbf3d8feee5a1a6da77c4b1f15 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Fri, 25 Jan 2019 01:10:09 -0800 Subject: [PATCH 060/233] Update screenshots in readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6cb85ab..8b55687 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ Self-hosted file/media sharing website. ### Screenshots - - + Get release and run From 5037573eab449305d5b5d6ee672bb5c2da285bfe Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sat, 26 Jan 2019 08:56:35 +0000 Subject: [PATCH 061/233] Clean up build.sh and build for linux/arm64 (#158) * Clean up build.sh and build for linux/arm64 --- build.sh | 141 ++++++++++++++++++++++--------------------------------- 1 file changed, 57 insertions(+), 84 deletions(-) diff --git a/build.sh b/build.sh index f7a4115..c4fc7f4 100755 --- a/build.sh +++ b/build.sh @@ -1,94 +1,67 @@ #!/bin/bash +function build_binary_rice { + name="$1" + + for arch in amd64 386; do + GOOS=darwin GOARCH=$arch go build -o "$name"osx-$arch + rice append --exec "$name"osx-$arch + done + + for arch in amd64 386; do + GOOS=freebsd GOARCH=$arch go build -o "$name"freebsd-$arch + rice append --exec "$name"freebsd-$arch + done + + for arch in amd64 386; do + GOOS=openbsd GOARCH=$arch go build -o "$name"openbsd-$arch + rice append --exec "$name"openbsd-$arch + done + + for arch in arm arm64 amd64 386; do + GOOS=linux GOARCH=$arch go build -o "$name"linux-$arch + rice append --exec "$name"linux-$arch + done + + for arch in amd64 386; do + GOOS=windows GOARCH=$arch go build -o "$name"windows-$arch.exe + rice append --exec "$name"windows-$arch.exe + done +} + +function build_binary { + name="$1" + + for arch in amd64 386; do + GOOS=darwin GOARCH=$arch go build -o "$name"osx-$arch + done + + for arch in amd64 386; do + GOOS=freebsd GOARCH=$arch go build -o "$name"freebsd-$arch + done + + for arch in amd64 386; do + GOOS=openbsd GOARCH=$arch go build -o "$name"openbsd-$arch + done + + for arch in arm arm64 amd64 386; do + GOOS=linux GOARCH=$arch go build -o "$name"linux-$arch + done + + for arch in amd64 386; do + GOOS=windows GOARCH=$arch go build -o "$name"windows-$arch.exe + done +} + version="$1" -mkdir -p "binairies/""$version" -name="binairies/""$version""/linx-server-v""$version""_" - -GOOS=darwin GOARCH=amd64 go build -o "$name"osx-amd64 -rice append --exec "$name"osx-amd64 - -GOOS=darwin GOARCH=386 go build -o "$name"osx-386 -rice append --exec "$name"osx-386 - -GOOS=freebsd GOARCH=amd64 go build -o "$name"freebsd-amd64 -rice append --exec "$name"freebsd-amd64 - -GOOS=freebsd GOARCH=386 go build -o "$name"freebsd-386 -rice append --exec "$name"freebsd-386 - -GOOS=openbsd GOARCH=amd64 go build -o "$name"openbsd-amd64 -rice append --exec "$name"openbsd-amd64 - -GOOS=openbsd GOARCH=386 go build -o "$name"openbsd-386 -rice append --exec "$name"openbsd-386 - -GOOS=linux GOARCH=arm go build -o "$name"linux-arm -rice append --exec "$name"linux-arm - -GOOS=linux GOARCH=amd64 go build -o "$name"linux-amd64 -rice append --exec "$name"linux-amd64 - -GOOS=linux GOARCH=386 go build -o "$name"linux-386 -rice append --exec "$name"linux-386 - -GOOS=windows GOARCH=amd64 go build -o "$name"windows-amd64.exe -rice append --exec "$name"windows-amd64.exe - -GOOS=windows GOARCH=386 go build -o "$name"windows-386.exe -rice append --exec "$name"windows-386.exe +mkdir -p "binaries/""$version" +build_binary_rice "binaries/""$version""/linx-server-v""$version""_" cd linx-genkey -name="../binairies/""$version""/linx-genkey-v""$version""_" - -GOOS=darwin GOARCH=amd64 go build -o "$name"osx-amd64 - -GOOS=darwin GOARCH=386 go build -o "$name"osx-386 - -GOOS=freebsd GOARCH=amd64 go build -o "$name"freebsd-amd64 - -GOOS=freebsd GOARCH=386 go build -o "$name"freebsd-386 - -GOOS=openbsd GOARCH=amd64 go build -o "$name"openbsd-amd64 - -GOOS=openbsd GOARCH=386 go build -o "$name"openbsd-386 - -GOOS=linux GOARCH=arm go build -o "$name"linux-arm - -GOOS=linux GOARCH=amd64 go build -o "$name"linux-amd64 - -GOOS=linux GOARCH=386 go build -o "$name"linux-386 - -GOOS=windows GOARCH=amd64 go build -o "$name"windows-amd64.exe - -GOOS=windows GOARCH=386 go build -o "$name"windows-386.exe - +build_binary "../binaries/""$version""/linx-genkey-v""$version""_" cd .. - cd linx-cleanup -name="../binairies/""$version""/linx-cleanup-v""$version""_" - -GOOS=darwin GOARCH=amd64 go build -o "$name"osx-amd64 - -GOOS=darwin GOARCH=386 go build -o "$name"osx-386 - -GOOS=freebsd GOARCH=amd64 go build -o "$name"freebsd-amd64 - -GOOS=freebsd GOARCH=386 go build -o "$name"freebsd-386 - -GOOS=openbsd GOARCH=amd64 go build -o "$name"openbsd-amd64 - -GOOS=openbsd GOARCH=386 go build -o "$name"openbsd-386 - -GOOS=linux GOARCH=arm go build -o "$name"linux-arm - -GOOS=linux GOARCH=amd64 go build -o "$name"linux-amd64 - -GOOS=linux GOARCH=386 go build -o "$name"linux-386 - -GOOS=windows GOARCH=amd64 go build -o "$name"windows-amd64.exe - -GOOS=windows GOARCH=386 go build -o "$name"windows-386.exe - +build_binary "../binaries/""$version""/linx-cleanup-v""$version""_" cd .. From a79bc1898ac34dfa727dade2e80b41d535eb54f4 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Sat, 26 Jan 2019 01:00:04 -0800 Subject: [PATCH 062/233] Add binaries/ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bd9da44..37e1e54 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ _testmain.go linx-server files/ meta/ +binaries/ linx-cleanup From 8f3108148b53f078fb19e0d2c9ac04acba8f7c8d Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sat, 26 Jan 2019 10:04:32 +0000 Subject: [PATCH 063/233] Add option to force random filenames (fixes #86) (#159) --- README.md | 1 + pages.go | 11 ++- server.go | 3 + server_test.go | 75 +++++++++++++++++++ static/js/upload.js | 175 ++++++++++++++++++++++--------------------- templates/API.html | 14 ++-- templates/index.html | 2 +- templates/paste.html | 2 +- upload.go | 29 +++++-- 9 files changed, 209 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 8b55687..bd1d9eb 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ allowhotlink = true - ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN") - ```-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 #### Storage backends The following storage backends are available: diff --git a/pages.go b/pages.go index bb38f37..6fcc934 100644 --- a/pages.go +++ b/pages.go @@ -21,8 +21,9 @@ const ( func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { err := renderTemplate(Templates["index.html"], pongo2.Context{ - "maxsize": Config.maxSize, - "expirylist": listExpirationTimes(), + "maxsize": Config.maxSize, + "expirylist": listExpirationTimes(), + "forcerandom": Config.forceRandomFilename, }, r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -31,7 +32,8 @@ func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) { err := renderTemplate(Templates["paste.html"], pongo2.Context{ - "expirylist": listExpirationTimes(), + "expirylist": listExpirationTimes(), + "forcerandom": Config.forceRandomFilename, }, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") @@ -40,7 +42,8 @@ func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) { func apiDocHandler(c web.C, w http.ResponseWriter, r *http.Request) { err := renderTemplate(Templates["API.html"], pongo2.Context{ - "siteurl": getSiteURL(r), + "siteurl": getSiteURL(r), + "forcerandom": Config.forceRandomFilename, }, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") diff --git a/server.go b/server.go index 7883a07..e4e1661 100644 --- a/server.go +++ b/server.go @@ -65,6 +65,7 @@ var Config struct { s3Region string s3Bucket string s3ForcePathStyle bool + forceRandomFilename bool } var Templates = make(map[string]*pongo2.Template) @@ -268,6 +269,8 @@ func main() { "S3 bucket to use for files and metadata") flag.BoolVar(&Config.s3ForcePathStyle, "s3-force-path-style", false, "Force path-style addressing for S3 (e.g. https://s3.amazonaws.com/linx/example.txt)") + flag.BoolVar(&Config.forceRandomFilename, "force-random-filename", false, + "Force all uploads to use a random filename") iniflags.Parse() diff --git a/server_test.go b/server_test.go index a1ec853..fc225ce 100644 --- a/server_test.go +++ b/server_test.go @@ -763,6 +763,32 @@ func TestPutRandomizedUpload(t *testing.T) { } } +func TestPutForceRandomUpload(t *testing.T) { + mux := setup() + w := httptest.NewRecorder() + + oldFRF := Config.forceRandomFilename + Config.forceRandomFilename = true + filename := "randomizeme.file" + + req, err := http.NewRequest("PUT", "/upload/"+filename, strings.NewReader("File content")) + if err != nil { + t.Fatal(err) + } + + // while this should also work without this header, let's try to force + // the randomized filename off to be sure + req.Header.Set("Linx-Randomize", "no") + + mux.ServeHTTP(w, req) + + if w.Body.String() == Config.siteURL+filename { + t.Fatal("Filename was not random") + } + + Config.forceRandomFilename = oldFRF +} + func TestPutNoExtensionUpload(t *testing.T) { mux := setup() w := httptest.NewRecorder() @@ -1013,6 +1039,55 @@ func TestPutAndOverwrite(t *testing.T) { } } +func TestPutAndOverwriteForceRandom(t *testing.T) { + var myjson RespOkJSON + + mux := setup() + w := httptest.NewRecorder() + + oldFRF := Config.forceRandomFilename + Config.forceRandomFilename = true + + req, err := http.NewRequest("PUT", "/upload", strings.NewReader("File content")) + if err != nil { + t.Fatal(err) + } + + req.Header.Set("Accept", "application/json") + + mux.ServeHTTP(w, req) + + err = json.Unmarshal([]byte(w.Body.String()), &myjson) + if err != nil { + t.Fatal(err) + } + + // Overwrite it + w = httptest.NewRecorder() + req, err = http.NewRequest("PUT", "/upload/"+myjson.Filename, strings.NewReader("New file content")) + req.Header.Set("Linx-Delete-Key", myjson.Delete_Key) + mux.ServeHTTP(w, req) + + if w.Code != 200 { + t.Fatal("Status code was not 200, but " + strconv.Itoa(w.Code)) + } + + // Make sure it's the new file + w = httptest.NewRecorder() + req, err = http.NewRequest("GET", "/"+Config.selifPath+myjson.Filename, nil) + mux.ServeHTTP(w, req) + + if w.Code == 404 { + t.Fatal("Status code was 404") + } + + if w.Body.String() != "New file content" { + t.Fatal("File did not contain 'New file content") + } + + Config.forceRandomFilename = oldFRF +} + func TestPutAndSpecificDelete(t *testing.T) { var myjson RespOkJSON diff --git a/static/js/upload.js b/static/js/upload.js index 159bad2..125123c 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -1,51 +1,54 @@ // @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later Dropzone.options.dropzone = { - init: function() { - var dzone = document.getElementById("dzone"); - dzone.style.display = "block"; - }, - addedfile: function(file) { - var upload = document.createElement("div"); - upload.className = "upload"; + init: function() { + var dzone = document.getElementById("dzone"); + dzone.style.display = "block"; + }, + addedfile: function(file) { + var upload = document.createElement("div"); + upload.className = "upload"; - var fileLabel = document.createElement("span"); - fileLabel.innerHTML = file.name; - file.fileLabel = fileLabel; - upload.appendChild(fileLabel); + var fileLabel = document.createElement("span"); + fileLabel.innerHTML = file.name; + file.fileLabel = fileLabel; + upload.appendChild(fileLabel); - var fileActions = document.createElement("div"); - fileActions.className = "right"; - file.fileActions = fileActions; - upload.appendChild(fileActions); + var fileActions = document.createElement("div"); + fileActions.className = "right"; + file.fileActions = fileActions; + upload.appendChild(fileActions); - var cancelAction = document.createElement("span"); - cancelAction.className = "cancel"; - cancelAction.innerHTML = "Cancel"; - cancelAction.addEventListener('click', function(ev) { - this.removeFile(file); - }.bind(this)); - file.cancelActionElement = cancelAction; - fileActions.appendChild(cancelAction); + var cancelAction = document.createElement("span"); + cancelAction.className = "cancel"; + cancelAction.innerHTML = "Cancel"; + cancelAction.addEventListener('click', function(ev) { + this.removeFile(file); + }.bind(this)); + file.cancelActionElement = cancelAction; + fileActions.appendChild(cancelAction); - var progress = document.createElement("span"); - file.progressElement = progress; - fileActions.appendChild(progress); + var progress = document.createElement("span"); + file.progressElement = progress; + fileActions.appendChild(progress); - file.uploadElement = upload; + file.uploadElement = upload; - document.getElementById("uploads").appendChild(upload); - }, - uploadprogress: function(file, p, bytesSent) { - p = parseInt(p); - file.progressElement.innerHTML = p + "%"; - file.uploadElement.setAttribute("style", 'background-image: -webkit-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -moz-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -ms-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -o-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%)'); - }, - sending: function(file, xhr, formData) { - formData.append("randomize", document.getElementById("randomize").checked); - formData.append("expires", document.getElementById("expires").value); - }, - success: function(file, resp) { + document.getElementById("uploads").appendChild(upload); + }, + uploadprogress: function(file, p, bytesSent) { + p = parseInt(p); + file.progressElement.innerHTML = p + "%"; + file.uploadElement.setAttribute("style", 'background-image: -webkit-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -moz-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -ms-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -o-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%)'); + }, + sending: function(file, xhr, formData) { + var randomize = document.getElementById("randomize"); + if(randomize != null) { + formData.append("randomize", randomize.checked); + } + formData.append("expires", document.getElementById("expires").value); + }, + success: function(file, resp) { file.fileActions.removeChild(file.progressElement); var fileLabelLink = document.createElement("a"); @@ -59,61 +62,61 @@ Dropzone.options.dropzone = { var deleteAction = document.createElement("span"); deleteAction.innerHTML = "Delete"; deleteAction.className = "cancel"; - deleteAction.addEventListener('click', function(ev) { - xhr = new XMLHttpRequest(); - xhr.open("DELETE", resp.url, true); - xhr.setRequestHeader("Linx-Delete-Key", resp.delete_key); - xhr.onreadystatechange = function(file) { - if (xhr.readyState == 4 && xhr.status === 200) { - var text = document.createTextNode("Deleted "); - file.fileLabel.insertBefore(text, file.fileLabelLink); - file.fileLabel.className = "deleted"; - file.fileActions.removeChild(file.cancelActionElement); - } - }.bind(this, file); - xhr.send(); - }); - file.fileActions.removeChild(file.cancelActionElement); - file.cancelActionElement = deleteAction; - file.fileActions.appendChild(deleteAction); - }, - error: function(file, resp, xhrO) { + deleteAction.addEventListener('click', function(ev) { + xhr = new XMLHttpRequest(); + xhr.open("DELETE", resp.url, true); + xhr.setRequestHeader("Linx-Delete-Key", resp.delete_key); + xhr.onreadystatechange = function(file) { + if (xhr.readyState == 4 && xhr.status === 200) { + var text = document.createTextNode("Deleted "); + file.fileLabel.insertBefore(text, file.fileLabelLink); + file.fileLabel.className = "deleted"; + file.fileActions.removeChild(file.cancelActionElement); + } + }.bind(this, file); + xhr.send(); + }); + file.fileActions.removeChild(file.cancelActionElement); + file.cancelActionElement = deleteAction; + file.fileActions.appendChild(deleteAction); + }, + error: function(file, resp, xhrO) { file.fileActions.removeChild(file.cancelActionElement); file.fileActions.removeChild(file.progressElement); - if (file.status === "canceled") { - file.fileLabel.innerHTML = file.name + ": Canceled "; - } - else { - if (resp.error) { - file.fileLabel.innerHTML = file.name + ": " + resp.error; - } - else if (resp.includes("Optional headers with the request

+{% if not forcerandom %}

Randomize the filename
Linx-Randomize: yes

+{% endif %}

Specify a custom deletion key
Linx-Delete-Key: mysecret

@@ -56,30 +58,30 @@ {% if using_auth %}
$ curl -H "Linx-Api-Key: mysecretkey" -T myphoto.jpg {{ siteurl }}upload/  
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}7z4h4ut.jpg{% endif %} {% else %}
$ curl -T myphoto.jpg {{ siteurl }}upload/  
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}wtq7pan.jpg{% endif %} {% endif %}

Uploading myphoto.jpg with an expiry of 20 minutes

{% if using_auth %}
$ curl -H "Linx-Api-Key: mysecretkey" -H "Linx-Expiry: 1200" -T myphoto.jpg {{ siteurl }}upload/
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}jm295snf.jpg{% endif %} {% else %}
$ curl -H "Linx-Expiry: 1200" -T myphoto.jpg {{ siteurl }}upload/
-{{ siteurl }}myphoto.jpg
+{{ siteurl }}{% if not forcerandom %}myphoto.jpg{% else %}1doym9u2.jpg{% endif %} {% endif %}

Uploading myphoto.jpg with a random filename and getting a json response:

{% if using_auth %} -
$ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/  
+			
$ curl -H "Linx-Api-Key: mysecretkey" -H "Accept: application/json"{% if not forcerandom %} -H "Linx-Randomize: yes"{% endif %} -T myphoto.jpg {{ siteurl }}upload/  
 {"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
 "sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"}
{% else %} -
$ curl -H "Accept: application/json" -H "Linx-Randomize: yes" -T myphoto.jpg {{ siteurl }}upload/  
+			
$ curl -H "Accept: application/json"{% if not forcerandom %} -H "Linx-Randomize: yes"{% endif %} -T myphoto.jpg {{ siteurl }}upload/  
 {"delete_key":"...","expiry":"0","filename":"f34h4iu.jpg","mimetype":"image/jpeg",
 "sha256sum":"...","size":"...","url":"{{ siteurl }}f34h4iu.jpg"}
{% endif %} diff --git a/templates/index.html b/templates/index.html index d423879..2843109 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,7 +17,7 @@
- +
diff --git a/upload.go b/upload.go index d46c4d5..6533bfe 100644 --- a/upload.go +++ b/upload.go @@ -222,11 +222,14 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { return upload, FileTooLargeError } - // Determine the appropriate filename, then write to disk + // Determine the appropriate filename barename, extension := barePlusExt(upReq.filename) + randomize := false + // Randomize the "barename" (filename without extension) if needed if upReq.randomBarename || len(barename) == 0 { barename = generateBarename() + randomize = true } var header []byte @@ -259,16 +262,32 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { if merr == nil { if upReq.deleteKey == metad.DeleteKey { fileexists = false + } else if Config.forceRandomFilename == true { + // the file exists + // the delete key doesn't match + // force random filenames is enabled + randomize = true } } + } else if Config.forceRandomFilename == true { + // the file doesn't exist + // force random filenames is enabled + randomize = true + + // set fileexists to true to generate a new barename + fileexists = true } for fileexists { - counter, err := strconv.Atoi(string(barename[len(barename)-1])) - if err != nil { - barename = barename + "1" + if randomize { + barename = generateBarename() } else { - barename = barename[:len(barename)-1] + strconv.Itoa(counter+1) + counter, err := strconv.Atoi(string(barename[len(barename)-1])) + if err != nil { + barename = barename + "1" + } else { + barename = barename[:len(barename)-1] + strconv.Itoa(counter+1) + } } upload.Filename = strings.Join([]string{barename, extension}, ".") From cde964ffe063a613b47b2c47c6a024431ead8f56 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Sat, 26 Jan 2019 02:12:49 -0800 Subject: [PATCH 064/233] Hide filename input when force random is on --- display.go | 17 +++++++++-------- templates/display/bin.html | 2 +- templates/display/story.html | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/display.go b/display.go index 7258904..feb16da 100644 --- a/display.go +++ b/display.go @@ -131,14 +131,15 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { } err = renderTemplate(tpl, pongo2.Context{ - "mime": metadata.Mimetype, - "filename": fileName, - "size": sizeHuman, - "expiry": expiryHuman, - "expirylist": listExpirationTimes(), - "extra": extra, - "lines": lines, - "files": metadata.ArchiveFiles, + "mime": metadata.Mimetype, + "filename": fileName, + "size": sizeHuman, + "expiry": expiryHuman, + "expirylist": listExpirationTimes(), + "extra": extra, + "forcerandom": Config.forceRandomFilename, + "lines": lines, + "files": metadata.ArchiveFiles, }, r, w) if err != nil { diff --git a/templates/display/bin.html b/templates/display/bin.html index bd029a2..dee64d8 100644 --- a/templates/display/bin.html +++ b/templates/display/bin.html @@ -22,7 +22,7 @@
- . + {% if not forcerandom %}{% endif %}.
diff --git a/templates/display/story.html b/templates/display/story.html index 20e772c..1ef96b1 100644 --- a/templates/display/story.html +++ b/templates/display/story.html @@ -20,7 +20,7 @@
- . + {% if not forcerandom %}{% endif %}.
From f46b61399bade712f0f77eef5d7762450613c4f3 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Sun, 27 Jan 2019 00:32:37 +0000 Subject: [PATCH 065/233] Fix broken page when file is missing (#160) With the localfs backend, it's possible for a file to be removed but its metadata file to remain intact. In this case, viewing the selif URL for that file would return a broken page with two error pages stacked on top of each other. This changes fixes that by replacing the output in that case with a single "Unable to open file." error message. --- fileserve.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fileserve.go b/fileserve.go index a3a249e..8948d45 100644 --- a/fileserve.go +++ b/fileserve.go @@ -38,8 +38,12 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("Referrer-Policy", Config.fileReferrerPolicy) _, reader, err := storageBackend.Get(fileName) - if err != nil { - oopsHandler(c, w, r, RespAUTO, err.Error()) + if err == backends.NotFoundErr { + notFoundHandler(c, w, r) + return + } else if err != nil { + oopsHandler(c, w, r, RespAUTO, "Unable to open file.") + return } w.Header().Set("Content-Type", metadata.Mimetype) From 73f127306c15789eb1da4b37b6f2bc49061d0fcf Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Tue, 29 Jan 2019 07:00:08 +0000 Subject: [PATCH 066/233] Improve UI a bit (#161) * Remove right margin from expiration dropdown on index * Use flexbox for bin/story display * Move Paste/Save button after expire dropdown, instead of before --- static/css/linx.css | 33 +++++----- static/js/bin.js | 116 +++++++++++++++++------------------ templates/base.html | 2 +- templates/display/base.html | 4 +- templates/display/bin.html | 23 ++++--- templates/display/story.html | 20 +++--- templates/paste.html | 6 +- 7 files changed, 105 insertions(+), 99 deletions(-) diff --git a/static/css/linx.css b/static/css/linx.css index 82e256a..15abee4 100644 --- a/static/css/linx.css +++ b/static/css/linx.css @@ -70,11 +70,21 @@ body { } #info { + background-color: white; + padding: 5px; +} + +.info-flex { display: flex; flex-wrap: wrap; + align-items: baseline; justify-content: space-between; - background-color: white; - padding: 5px 5px 5px 5px; +} + +.info-actions { + margin-left: 15px; + font-size: 13px; + text-align: right; } #info #extension, @@ -82,10 +92,6 @@ body { width: 40px; } -#info .text-right { - font-size: 13px; -} - #info a { text-decoration: none; color: #556A7F; @@ -246,11 +252,7 @@ body { justify-content: space-between; width: 100%; margin-top: 5px; - font-size:13px; -} - -#choices label:first-child { - margin-right: 15px; + font-size: 13px; } #expiry { @@ -295,14 +297,11 @@ body { } - #info input[type=text] { border: 1px solid #eaeaea; color: #556A7F; - border-radius: 4px 4px 4px 4px; - padding-left: 4px; - padding-right: 4px; - height: 15px; + padding: 2px 4px; + font-family: Arial, Helvetica, sans-serif; } .storygreen { @@ -357,4 +356,4 @@ body { height: 800px; font-size: 13px; } -/* }}} */ \ No newline at end of file +/* }}} */ diff --git a/static/js/bin.js b/static/js/bin.js index 7aed334..11c6c21 100644 --- a/static/js/bin.js +++ b/static/js/bin.js @@ -1,58 +1,58 @@ -// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later - -var navlist = document.getElementById("info").getElementsByClassName("text-right")[0]; - -init(); - -function init() { - var editA = document.createElement('a'); - - editA.setAttribute("href", "#"); - editA.addEventListener('click', function(ev) { - edit(ev); - return false; - }); - editA.innerHTML = "edit"; - - var separator = document.createTextNode(" | "); - navlist.insertBefore(editA, navlist.firstChild); - navlist.insertBefore(separator, navlist.children[1]); - - document.getElementById('save').addEventListener('click', paste); - document.getElementById('wordwrap').addEventListener('click', wrap); -} - -function edit(ev) { - ev.preventDefault(); - - navlist.remove(); - document.getElementById("filename").remove(); - document.getElementById("editform").style.display = "block"; - - var normalcontent = document.getElementById("normal-content"); - normalcontent.removeChild(document.getElementById("normal-code")); - - var editordiv = document.getElementById("inplace-editor"); - editordiv.style.display = "block"; - editordiv.addEventListener('keydown', handleTab); -} - -function paste(ev) { - var editordiv = document.getElementById("inplace-editor"); - document.getElementById("newcontent").value = editordiv.value; - document.forms["reply"].submit(); -} - -function wrap(ev) { - if (document.getElementById("wordwrap").checked) { - document.getElementById("codeb").style.wordWrap = "break-word"; - document.getElementById("codeb").style.whiteSpace = "pre-wrap"; - } - - else { - document.getElementById("codeb").style.wordWrap = "normal"; - document.getElementById("codeb").style.whiteSpace = "pre"; - } -} - -// @license-end +// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later + +var navlist = document.getElementById("info").getElementsByClassName("info-actions")[0]; + +init(); + +function init() { + var editA = document.createElement('a'); + + editA.setAttribute("href", "#"); + editA.addEventListener('click', function(ev) { + edit(ev); + return false; + }); + editA.innerHTML = "edit"; + + var separator = document.createTextNode(" | "); + navlist.insertBefore(editA, navlist.firstChild); + navlist.insertBefore(separator, navlist.children[1]); + + document.getElementById('save').addEventListener('click', paste); + document.getElementById('wordwrap').addEventListener('click', wrap); +} + +function edit(ev) { + ev.preventDefault(); + + navlist.remove(); + document.getElementById("filename").remove(); + document.getElementById("editform").style.display = "block"; + + var normalcontent = document.getElementById("normal-content"); + normalcontent.removeChild(document.getElementById("normal-code")); + + var editordiv = document.getElementById("inplace-editor"); + editordiv.style.display = "block"; + editordiv.addEventListener('keydown', handleTab); +} + +function paste(ev) { + var editordiv = document.getElementById("inplace-editor"); + document.getElementById("newcontent").value = editordiv.value; + document.forms["reply"].submit(); +} + +function wrap(ev) { + if (document.getElementById("wordwrap").checked) { + document.getElementById("codeb").style.wordWrap = "break-word"; + document.getElementById("codeb").style.whiteSpace = "pre-wrap"; + } + + else { + document.getElementById("codeb").style.wordWrap = "normal"; + document.getElementById("codeb").style.whiteSpace = "pre"; + } +} + +// @license-end diff --git a/templates/base.html b/templates/base.html index 5392f8d..d1411d4 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,7 +4,7 @@ {% block title %}{{ sitename }}{% endblock %} - + {% block head %}{% endblock %} diff --git a/templates/display/base.html b/templates/display/base.html index 011534c..935979f 100644 --- a/templates/display/base.html +++ b/templates/display/base.html @@ -6,12 +6,12 @@ {% block content %} -
+
{{ filename }}
-
+
{% if expiry %} file expires in {{ expiry }} | {% endif %} diff --git a/templates/display/bin.html b/templates/display/bin.html index dee64d8..c3ee97c 100644 --- a/templates/display/bin.html +++ b/templates/display/bin.html @@ -11,24 +11,27 @@ {% block infoleft %}
-
-
- - {% endif %}. +
+
+ + + +
- - {% if not forcerandom %}{% endif %}.
{% endblock %} -{%block infomore %} +{% block infomore %} | {% endblock %} @@ -45,5 +48,5 @@ {% endif %} - + {% endblock %} diff --git a/templates/display/story.html b/templates/display/story.html index 1ef96b1..cb30323 100644 --- a/templates/display/story.html +++ b/templates/display/story.html @@ -9,18 +9,22 @@ {% block infoleft %}
-
-
- - {% endif %}. +
+
+ + + +
- {% if not forcerandom %}{% endif %}.
@@ -38,5 +42,5 @@ - + {% endblock %} diff --git a/templates/paste.html b/templates/paste.html index ab7965f..245b7a3 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -3,18 +3,18 @@ {% block content %}
-
+
{% if not forcerandom %}{% endif %}.
- +
From 770cb204793a823057c15a53e85b222af6178f34 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Thu, 31 Jan 2019 06:52:43 +0000 Subject: [PATCH 067/233] Add support for conditional requests (#162) This change pulls in some code copied from net/http's fs.go so that we can support If-Match/If-None-Match requests. This will make it easy to put a caching proxy in front of linx-server instances. Request validation will still happen as long as the proxy can contact the origin, so expiration and deletion will still work as expected under normal circumstances. --- fileserve.go | 30 +++--- httputil/LICENSE | 27 +++++ httputil/conditional.go | 218 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 httputil/LICENSE create mode 100644 httputil/conditional.go diff --git a/fileserve.go b/fileserve.go index 8948d45..202e477 100644 --- a/fileserve.go +++ b/fileserve.go @@ -1,14 +1,17 @@ package main import ( + "fmt" "io" "net/http" "net/url" "strconv" "strings" + "time" "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/expiry" + "github.com/andreimarcu/linx-server/httputil" "github.com/zenazn/goji/web" ) @@ -37,21 +40,22 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Security-Policy", Config.fileContentSecurityPolicy) w.Header().Set("Referrer-Policy", Config.fileReferrerPolicy) - _, reader, err := storageBackend.Get(fileName) - if err == backends.NotFoundErr { - notFoundHandler(c, w, r) - return - } else if err != nil { - oopsHandler(c, w, r, RespAUTO, "Unable to open file.") + w.Header().Set("Content-Type", metadata.Mimetype) + w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10)) + w.Header().Set("Etag", fmt.Sprintf("\"%s\"", metadata.Sha256sum)) + w.Header().Set("Cache-Control", "public, no-cache") + + modtime := time.Unix(0, 0) + if done := httputil.CheckPreconditions(w, r, modtime); done == true { return } - w.Header().Set("Content-Type", metadata.Mimetype) - w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10)) - w.Header().Set("Etag", metadata.Sha256sum) - w.Header().Set("Cache-Control", "max-age=0") - if r.Method != "HEAD" { + _, reader, err := storageBackend.Get(fileName) + if err != nil { + oopsHandler(c, w, r, RespAUTO, "Unable to open file.") + return + } defer reader.Close() if _, err = io.CopyN(w, reader, metadata.Size); err != nil { @@ -77,8 +81,8 @@ func staticHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Etag", timeStartedStr) - w.Header().Set("Cache-Control", "max-age=86400") + w.Header().Set("Etag", fmt.Sprintf("\"%s\"", timeStartedStr)) + w.Header().Set("Cache-Control", "public, max-age=86400") http.ServeContent(w, r, filePath, timeStarted, file) return } diff --git a/httputil/LICENSE b/httputil/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/httputil/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/httputil/conditional.go b/httputil/conditional.go new file mode 100644 index 0000000..999b1ac --- /dev/null +++ b/httputil/conditional.go @@ -0,0 +1,218 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// HTTP file system request handler + +package httputil + +import ( + "net/http" + "net/textproto" + "strings" + "time" +) + +// scanETag determines if a syntactically valid ETag is present at s. If so, +// the ETag and remaining text after consuming ETag is returned. Otherwise, +// it returns "", "". +func scanETag(s string) (etag string, remain string) { + s = textproto.TrimString(s) + start := 0 + if strings.HasPrefix(s, "W/") { + start = 2 + } + if len(s[start:]) < 2 || s[start] != '"' { + return "", "" + } + // ETag is either W/"text" or "text". + // See RFC 7232 2.3. + for i := start + 1; i < len(s); i++ { + c := s[i] + switch { + // Character values allowed in ETags. + case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80: + case c == '"': + return s[:i+1], s[i+1:] + default: + return "", "" + } + } + return "", "" +} + +// etagStrongMatch reports whether a and b match using strong ETag comparison. +// Assumes a and b are valid ETags. +func etagStrongMatch(a, b string) bool { + return a == b && a != "" && a[0] == '"' +} + +// etagWeakMatch reports whether a and b match using weak ETag comparison. +// Assumes a and b are valid ETags. +func etagWeakMatch(a, b string) bool { + return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/") +} + +// condResult is the result of an HTTP request precondition check. +// See https://tools.ietf.org/html/rfc7232 section 3. +type condResult int + +const ( + condNone condResult = iota + condTrue + condFalse +) + +func checkIfMatch(w http.ResponseWriter, r *http.Request) condResult { + im := r.Header.Get("If-Match") + if im == "" { + return condNone + } + for { + im = textproto.TrimString(im) + if len(im) == 0 { + break + } + if im[0] == ',' { + im = im[1:] + continue + } + if im[0] == '*' { + return condTrue + } + etag, remain := scanETag(im) + if etag == "" { + break + } + if etagStrongMatch(etag, w.Header().Get("Etag")) { + return condTrue + } + im = remain + } + + return condFalse +} + +func checkIfUnmodifiedSince(r *http.Request, modtime time.Time) condResult { + ius := r.Header.Get("If-Unmodified-Since") + if ius == "" || isZeroTime(modtime) { + return condNone + } + if t, err := http.ParseTime(ius); err == nil { + // The Date-Modified header truncates sub-second precision, so + // use mtime < t+1s instead of mtime <= t to check for unmodified. + if modtime.Before(t.Add(1 * time.Second)) { + return condTrue + } + return condFalse + } + return condNone +} + +func checkIfNoneMatch(w http.ResponseWriter, r *http.Request) condResult { + inm := r.Header.Get("If-None-Match") + if inm == "" { + return condNone + } + buf := inm + for { + buf = textproto.TrimString(buf) + if len(buf) == 0 { + break + } + if buf[0] == ',' { + buf = buf[1:] + } + if buf[0] == '*' { + return condFalse + } + etag, remain := scanETag(buf) + if etag == "" { + break + } + if etagWeakMatch(etag, w.Header().Get("Etag")) { + return condFalse + } + buf = remain + } + return condTrue +} + +func checkIfModifiedSince(r *http.Request, modtime time.Time) condResult { + if r.Method != "GET" && r.Method != "HEAD" { + return condNone + } + ims := r.Header.Get("If-Modified-Since") + if ims == "" || isZeroTime(modtime) { + return condNone + } + t, err := http.ParseTime(ims) + if err != nil { + return condNone + } + // The Date-Modified header truncates sub-second precision, so + // use mtime < t+1s instead of mtime <= t to check for unmodified. + if modtime.Before(t.Add(1 * time.Second)) { + return condFalse + } + return condTrue +} + +var unixEpochTime = time.Unix(0, 0) + +// isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0). +func isZeroTime(t time.Time) bool { + return t.IsZero() || t.Equal(unixEpochTime) +} + +func setLastModified(w http.ResponseWriter, modtime time.Time) { + if !isZeroTime(modtime) { + w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) + } +} + +func writeNotModified(w http.ResponseWriter) { + // RFC 7232 section 4.1: + // a sender SHOULD NOT generate representation metadata other than the + // above listed fields unless said metadata exists for the purpose of + // guiding cache updates (e.g., Last-Modified might be useful if the + // response does not have an ETag field). + h := w.Header() + delete(h, "Content-Type") + delete(h, "Content-Length") + if h.Get("Etag") != "" { + delete(h, "Last-Modified") + } + w.WriteHeader(http.StatusNotModified) +} + +// CheckPreconditions evaluates request preconditions and reports whether a precondition +// resulted in sending StatusNotModified or StatusPreconditionFailed. +func CheckPreconditions(w http.ResponseWriter, r *http.Request, modtime time.Time) (done bool) { + // This function carefully follows RFC 7232 section 6. + ch := checkIfMatch(w, r) + if ch == condNone { + ch = checkIfUnmodifiedSince(r, modtime) + } + if ch == condFalse { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + switch checkIfNoneMatch(w, r) { + case condFalse: + if r.Method == "GET" || r.Method == "HEAD" { + writeNotModified(w) + return true + } else { + w.WriteHeader(http.StatusPreconditionFailed) + return true + } + case condNone: + if checkIfModifiedSince(r, modtime) == condFalse { + writeNotModified(w) + return true + } + } + + return false +} From 2c0b2b2e79518b54dbd2ed4ae93919f7fc379221 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 14 Mar 2019 10:58:17 -0700 Subject: [PATCH 068/233] README reordering --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd1d9eb..3b46bb6 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,12 @@ allowhotlink = true - ```-nologs``` -- (optionally) disable request logs in stdout - ```-force-random-filename``` -- (optionally) force the use of random filenames +#### 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 +- ```-remoteauthfile path/to/remoteauthfile``` -- (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys + +A helper utility ```linx-genkey``` is provided which hashes keys to the format required in the auth files. + #### Storage backends The following storage backends are available: @@ -73,12 +79,6 @@ The following storage backends are available: #### Use with fastcgi - ```-fastcgi``` -- serve through fastcgi -#### 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 -- ```-remoteauthfile path/to/remoteauthfile``` -- (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys - -A helper utility ```linx-genkey``` is provided which hashes keys to the format required in the auth files. - Cleaning up expired files ------------------------- From 8098b7e39e2fea603d1b9fbe4883e858bd5b5f45 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Tue, 9 Apr 2019 13:28:18 -0700 Subject: [PATCH 069/233] Add PutMetadata function to storage backends (#171) * Add PutMetadata function to storage backends This function is not currently used, but it will be useful for helper scripts that need to regenerate metadata on the fly, especially scripts to migrate between storage backends. In the future, we can also use it to automatically regenerate metadata if it is found to be missing or corrupted. * Add PutMetadata function to storage backend interface and implementations * Rework metadata generation to be more efficient and work better with the PutMetadata function * Add a basic test for metadata generation * Change PutMetadata to take a Metadata type instead It's unlikely that this function is useful if it always regenerates the metadata. Instead, the caller should do that if it needs. --- backends/localfs/localfs.go | 20 +++++++++-- backends/s3/s3.go | 48 +++++++++++++++++---------- backends/storage.go | 1 + helpers/helpers.go | 66 +++++++++++++++++++++++-------------- helpers/helpers_test.go | 29 ++++++++++++++++ 5 files changed, 119 insertions(+), 45 deletions(-) create mode 100644 helpers/helpers_test.go diff --git a/backends/localfs/localfs.go b/backends/localfs/localfs.go index 3f6f5ad..47187b6 100644 --- a/backends/localfs/localfs.go +++ b/backends/localfs/localfs.go @@ -126,11 +126,16 @@ func (b LocalfsBackend) Put(key string, r io.Reader, expiry time.Time, deleteKey return m, err } + dst.Seek(0 ,0) + m, err = helpers.GenerateMetadata(dst) + if err != nil { + os.Remove(filePath) + return + } + dst.Seek(0 ,0) + m.Expiry = expiry m.DeleteKey = deleteKey - m.Size = bytes - m.Mimetype, _ = helpers.DetectMime(dst) - m.Sha256sum, _ = helpers.Sha256sum(dst) m.ArchiveFiles, _ = helpers.ListArchiveFiles(m.Mimetype, m.Size, dst) err = b.writeMetadata(key, m) @@ -142,6 +147,15 @@ func (b LocalfsBackend) Put(key string, r io.Reader, expiry time.Time, deleteKey return } +func (b LocalfsBackend) PutMetadata(key string, m backends.Metadata) (err error) { + err = b.writeMetadata(key, m) + if err != nil { + return + } + + return +} + func (b LocalfsBackend) Size(key string) (int64, error) { fileInfo, err := os.Stat(path.Join(b.filesPath, key)) if err != nil { diff --git a/backends/s3/s3.go b/backends/s3/s3.go index 45067c1..ad040e1 100644 --- a/backends/s3/s3.go +++ b/backends/s3/s3.go @@ -18,13 +18,13 @@ import ( type S3Backend struct { bucket string - svc *s3.S3 + svc *s3.S3 } func (b S3Backend) Delete(key string) error { _, err := b.svc.DeleteObject(&s3.DeleteObjectInput{ Bucket: aws.String(b.bucket), - Key: aws.String(key), + Key: aws.String(key), }) if err != nil { return err @@ -35,7 +35,7 @@ func (b S3Backend) Delete(key string) error { func (b S3Backend) Exists(key string) (bool, error) { _, err := b.svc.HeadObject(&s3.HeadObjectInput{ Bucket: aws.String(b.bucket), - Key: aws.String(key), + Key: aws.String(key), }) return err == nil, err } @@ -44,7 +44,7 @@ func (b S3Backend) Head(key string) (metadata backends.Metadata, err error) { var result *s3.HeadObjectOutput result, err = b.svc.HeadObject(&s3.HeadObjectInput{ Bucket: aws.String(b.bucket), - Key: aws.String(key), + Key: aws.String(key), }) if err != nil { if aerr, ok := err.(awserr.Error); ok { @@ -63,7 +63,7 @@ func (b S3Backend) Get(key string) (metadata backends.Metadata, r io.ReadCloser, var result *s3.GetObjectOutput result, err = b.svc.GetObject(&s3.GetObjectInput{ Bucket: aws.String(b.bucket), - Key: aws.String(key), + Key: aws.String(key), }) if err != nil { if aerr, ok := err.(awserr.Error); ok { @@ -81,11 +81,11 @@ func (b S3Backend) Get(key string) (metadata backends.Metadata, r io.ReadCloser, func mapMetadata(m backends.Metadata) map[string]*string { return map[string]*string{ - "Expiry": aws.String(strconv.FormatInt(m.Expiry.Unix(), 10)), + "Expiry": aws.String(strconv.FormatInt(m.Expiry.Unix(), 10)), "Delete_key": aws.String(m.DeleteKey), - "Size": aws.String(strconv.FormatInt(m.Size, 10)), - "Mimetype": aws.String(m.Mimetype), - "Sha256sum": aws.String(m.Sha256sum), + "Size": aws.String(strconv.FormatInt(m.Size, 10)), + "Mimetype": aws.String(m.Mimetype), + "Sha256sum": aws.String(m.Sha256sum), } } @@ -122,19 +122,20 @@ func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey stri return m, err } + m, err = helpers.GenerateMetadata(r) + if err != nil { + return + } m.Expiry = expiry m.DeleteKey = deleteKey - m.Size = bytes - m.Mimetype, _ = helpers.DetectMime(tmpDst) - m.Sha256sum, _ = helpers.Sha256sum(tmpDst) // XXX: we may not be able to write this to AWS easily //m.ArchiveFiles, _ = helpers.ListArchiveFiles(m.Mimetype, m.Size, tmpDst) uploader := s3manager.NewUploaderWithClient(b.svc) input := &s3manager.UploadInput{ - Bucket: aws.String(b.bucket), - Key: aws.String(key), - Body: tmpDst, + Bucket: aws.String(b.bucket), + Key: aws.String(key), + Body: tmpDst, Metadata: mapMetadata(m), } _, err = uploader.Upload(input) @@ -145,10 +146,24 @@ func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey stri return } +func (b S3Backend) PutMetadata(key string, m backends.Metadata) (err error) { + _, err = b.svc.CopyObject(&s3.CopyObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + CopySource: aws.String("/" + b.bucket + "/" + key), + Metadata: mapMetadata(m), + }) + if err != nil { + return + } + + return +} + func (b S3Backend) Size(key string) (int64, error) { input := &s3.HeadObjectInput{ Bucket: aws.String(b.bucket), - Key: aws.String(key), + Key: aws.String(key), } result, err := b.svc.HeadObject(input) if err != nil { @@ -169,7 +184,6 @@ func (b S3Backend) List() ([]string, error) { return nil, err } - for _, object := range results.Contents { output = append(output, *object.Key) } diff --git a/backends/storage.go b/backends/storage.go index d40a2b9..fdd8cd6 100644 --- a/backends/storage.go +++ b/backends/storage.go @@ -12,6 +12,7 @@ type StorageBackend interface { Head(key string) (Metadata, error) Get(key string) (Metadata, io.ReadCloser, error) Put(key string, r io.Reader, expiry time.Time, deleteKey string) (Metadata, error) + PutMetadata(key string, m Metadata) error Size(key string) (int64, error) } diff --git a/helpers/helpers.go b/helpers/helpers.go index aef68ff..f51d998 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -1,49 +1,65 @@ package helpers import ( + "bytes" "encoding/hex" "io" "unicode" + "github.com/andreimarcu/linx-server/backends" "github.com/minio/sha256-simd" "gopkg.in/h2non/filetype.v1" ) -func DetectMime(r io.ReadSeeker) (string, error) { +func GenerateMetadata(r io.Reader) (m backends.Metadata, err error) { + // Since we don't have the ability to seek within a file, we can use a + // Buffer in combination with a TeeReader to keep a copy of the bytes + // we read when detecting the file type. These bytes are still needed + // to hash the file and determine its size and cannot be discarded. + var buf bytes.Buffer + teeReader := io.TeeReader(r, &buf) + // Get first 512 bytes for mimetype detection header := make([]byte, 512) + _, err = teeReader.Read(header) + if err != nil { + return + } - r.Seek(0, 0) - r.Read(header) - r.Seek(0, 0) + // Create a Hash and a MultiReader that includes the Buffer we created + // above along with the original Reader, which will have the rest of + // the file. + hasher := sha256.New() + multiReader := io.MultiReader(&buf, r) + // Copy everything into the Hash, then use the number of bytes written + // as the file size. + var readLen int64 + readLen, err = io.Copy(hasher, multiReader) + if err != nil { + return + } else { + m.Size += readLen + } + + // Get the hex-encoded string version of the Hash checksum + m.Sha256sum = hex.EncodeToString(hasher.Sum(nil)) + + // Use the bytes we extracted earlier and attempt to determine the file + // type kind, err := filetype.Match(header) if err != nil { - return "application/octet-stream", err + m.Mimetype = "application/octet-stream" + return m, err } else if kind.MIME.Value != "" { - return kind.MIME.Value, nil - } - - // Check if the file seems anything like text - if printable(header) { - return "text/plain", nil + m.Mimetype = kind.MIME.Value + } else if printable(header) { + m.Mimetype = "text/plain" } else { - return "application/octet-stream", nil - } -} - -func Sha256sum(r io.ReadSeeker) (string, error) { - hasher := sha256.New() - - r.Seek(0, 0) - _, err := io.Copy(hasher, r) - if err != nil { - return "", err + m.Mimetype = "application/octet-stream" } - r.Seek(0, 0) - - return hex.EncodeToString(hasher.Sum(nil)), nil + return } func printable(data []byte) bool { diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go new file mode 100644 index 0000000..800d0d2 --- /dev/null +++ b/helpers/helpers_test.go @@ -0,0 +1,29 @@ +package helpers + +import ( + "strings" + "testing" +) + +func TestGenerateMetadata(t *testing.T) { + r := strings.NewReader("This is my test content") + m, err := GenerateMetadata(r) + if err != nil { + t.Fatal(err) + } + + expectedSha256sum := "966152d20a77e739716a625373ee15af16e8f4aec631a329a27da41c204b0171" + if m.Sha256sum != expectedSha256sum { + t.Fatalf("Sha256sum was %q instead of expected value of %q", m.Sha256sum, expectedSha256sum) + } + + expectedMimetype := "text/plain" + if m.Mimetype != expectedMimetype { + t.Fatalf("Mimetype was %q instead of expected value of %q", m.Mimetype, expectedMimetype) + } + + expectedSize := int64(23) + if m.Size != expectedSize { + t.Fatalf("Size was %d instead of expected value of %d", m.Size, expectedSize) + } +} From 872340e0dc6f76fe2997520d276207ff8cd6da1c Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Thu, 9 May 2019 16:13:58 +0000 Subject: [PATCH 070/233] Fix PutMetadata with S3 backend (#176) It turns out that the S3 API expects the additional `MetadataDirective: REPLACE` option in order to update metadata. If this is not provided, then metadata will simply be copied from the source object, which is not what we wanted. --- backends/s3/s3.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backends/s3/s3.go b/backends/s3/s3.go index ad040e1..e39786a 100644 --- a/backends/s3/s3.go +++ b/backends/s3/s3.go @@ -148,10 +148,11 @@ func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey stri func (b S3Backend) PutMetadata(key string, m backends.Metadata) (err error) { _, err = b.svc.CopyObject(&s3.CopyObjectInput{ - Bucket: aws.String(b.bucket), - Key: aws.String(key), - CopySource: aws.String("/" + b.bucket + "/" + key), - Metadata: mapMetadata(m), + Bucket: aws.String(b.bucket), + Key: aws.String(key), + CopySource: aws.String("/" + b.bucket + "/" + key), + Metadata: mapMetadata(m), + MetadataDirective: aws.String("REPLACE"), }) if err != nil { return From 50c1bdc018653f83720830f01465cee5e2c726b3 Mon Sep 17 00:00:00 2001 From: mutantmonkey Date: Tue, 4 Jun 2019 16:35:52 +0000 Subject: [PATCH 071/233] Fix S3 uploads (#178) My previous change that reworked how metadata generation worked was not correct for two reasons: * It passed the original `io.Reader` (with no bytes remaining) to `GenerateMetadata` * It did not seek back to the beginning of the temporary file after writing to it, causing any subsequent reads to return EOF. This change fixes both issues and S3 uploads now work fine. We really ought to investigate adding test coverage to our S3 backend to avoid similar recurrences. --- backends/s3/s3.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/backends/s3/s3.go b/backends/s3/s3.go index e39786a..fc2a1b0 100644 --- a/backends/s3/s3.go +++ b/backends/s3/s3.go @@ -122,7 +122,12 @@ func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey stri return m, err } - m, err = helpers.GenerateMetadata(r) + _, err = tmpDst.Seek(0, 0) + if err != nil { + return m, err + } + + m, err = helpers.GenerateMetadata(tmpDst) if err != nil { return } @@ -131,6 +136,11 @@ func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey stri // XXX: we may not be able to write this to AWS easily //m.ArchiveFiles, _ = helpers.ListArchiveFiles(m.Mimetype, m.Size, tmpDst) + _, err = tmpDst.Seek(0, 0) + if err != nil { + return m, err + } + uploader := s3manager.NewUploaderWithClient(b.svc) input := &s3manager.UploadInput{ Bucket: aws.String(b.bucket), From bf090c51193490738b7cf08d3ac2643e19d6ac62 Mon Sep 17 00:00:00 2001 From: nia <29542929+niacat@users.noreply.github.com> Date: Thu, 13 Jun 2019 20:50:59 +0100 Subject: [PATCH 072/233] build.sh: build for netbsd (#181) --- build.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build.sh b/build.sh index c4fc7f4..a34085e 100755 --- a/build.sh +++ b/build.sh @@ -13,6 +13,11 @@ function build_binary_rice { rice append --exec "$name"freebsd-$arch done + for arch in arm amd64 386; do + GOOS=netbsd GOARCH=$arch go build -o "$name"netbsd-$arch + rice append --exec "$name"netbsd-$arch + done + for arch in amd64 386; do GOOS=openbsd GOARCH=$arch go build -o "$name"openbsd-$arch rice append --exec "$name"openbsd-$arch @@ -40,6 +45,10 @@ function build_binary { GOOS=freebsd GOARCH=$arch go build -o "$name"freebsd-$arch done + for arch in arm amd64 386; do + GOOS=netbsd GOARCH=$arch go build -o "$name"netbsd-$arch + done + for arch in amd64 386; do GOOS=openbsd GOARCH=$arch go build -o "$name"openbsd-$arch done From 0e763cb776ddfe1bd3252c3964f0e8f2a99f61e0 Mon Sep 17 00:00:00 2001 From: PaulC <212168+paulc@users.noreply.github.com> Date: Tue, 3 Dec 2019 02:24:11 +0000 Subject: [PATCH 073/233] Allow UNIX socket in FastCGI mode (check if bind starts with /) (#190) Allow UNIX socket in FastCGI mode (check if bind starts with /) --- server.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index e4e1661..71a9c4d 100644 --- a/server.go +++ b/server.go @@ -8,9 +8,11 @@ import ( "net/http/fcgi" "net/url" "os" + "os/signal" "regexp" "strconv" "strings" + "syscall" "time" "github.com/GeertJohan/go.rice" @@ -277,7 +279,27 @@ func main() { mux := setup() if Config.fastcgi { - listener, err := net.Listen("tcp", Config.bind) + var listener net.Listener + var err error + if Config.bind[0] == '/' { + // UNIX path + listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: Config.bind, Net: "unix"}) + cleanup := func() { + log.Print("Removing FastCGI socket") + os.Remove(Config.bind) + } + defer cleanup() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-sigs + log.Print("Signal: ", sig) + cleanup() + os.Exit(0) + }() + } else { + listener, err = net.Listen("tcp", Config.bind) + } if err != nil { log.Fatal("Could not bind: ", err) } From 85be5b078d9767f2b97ac7180d84e84aef2e19a5 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Mon, 2 Dec 2019 18:30:15 -0800 Subject: [PATCH 074/233] Add a max-height for videos display --- static/css/linx.css | 1 + 1 file changed, 1 insertion(+) diff --git a/static/css/linx.css b/static/css/linx.css index 15abee4..646c83b 100644 --- a/static/css/linx.css +++ b/static/css/linx.css @@ -330,6 +330,7 @@ body { .display-video { width: 800px; + max-height: 70vh; } .scrollable { From 1d583bcb489cefa0be3774080718aae2b00f9de2 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Mon, 2 Dec 2019 18:38:07 -0800 Subject: [PATCH 075/233] Fix #189 - Markdown lists don't show numbers/bullets --- static/css/github-markdown.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/css/github-markdown.css b/static/css/github-markdown.css index 6823d9c..6a27ed0 100644 --- a/static/css/github-markdown.css +++ b/static/css/github-markdown.css @@ -44,6 +44,10 @@ overflow: auto; } +.markdown-body li { + list-style: unset; +} + .markdown-body code, .markdown-body kbd, .markdown-body pre { From cf0a30f46a030859033e89006be3b96b8645886d Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 16 Jan 2020 16:26:25 -0800 Subject: [PATCH 076/233] Add URL to demo --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 3b46bb6..24ef4ea 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ linx-server Self-hosted file/media sharing website. +### Demo +You can see what it looks like using the demo: [https://demo.linx-server.net/](https://demo.linx-server.net/) + ### Features From b63274ad01e0ec2fe12742bece7881d9cc80ce39 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Mon, 17 Feb 2020 17:58:56 +0300 Subject: [PATCH 077/233] allow limiting access by passwords (fix #194) --- access.go | 138 ++++++++++++++++++++++++++++++++++++ backends/localfs/localfs.go | 20 +++--- backends/meta.go | 1 + backends/s3/s3.go | 9 ++- backends/storage.go | 2 +- display.go | 22 +----- fileserve.go | 10 +++ server.go | 7 +- templates.go | 3 +- templates/access.html | 11 +++ templates/index.html | 1 + upload.go | 7 +- 12 files changed, 197 insertions(+), 34 deletions(-) create mode 100644 access.go create mode 100644 templates/access.html diff --git a/access.go b/access.go new file mode 100644 index 0000000..bacea24 --- /dev/null +++ b/access.go @@ -0,0 +1,138 @@ +package main + +import ( + "encoding/json" + "errors" + "net/http" + "regexp" + "strings" + "time" + + "github.com/andreimarcu/linx-server/backends" + "github.com/flosch/pongo2" + "github.com/zenazn/goji/web" +) + +type accessKeySource int + +const ( + accessKeySourceNone accessKeySource = iota + accessKeySourceCookie + accessKeySourceHeader + accessKeySourceForm + accessKeySourceQuery +) + +const accessKeyHeaderName = "Linx-Access-Key" +const accessKeyParamName = "access_key" + +var ( + errInvalidAccessKey = errors.New("invalid access key") + + cliUserAgentRe = regexp.MustCompile("(?i)(lib)?curl|wget") +) + +func checkAccessKey(r *http.Request, metadata *backends.Metadata) (accessKeySource, error) { + key := metadata.AccessKey + if key == "" { + return accessKeySourceNone, nil + } + + cookieKey, err := r.Cookie(accessKeyHeaderName) + if err == nil { + if cookieKey.Value == key { + return accessKeySourceCookie, nil + } + return accessKeySourceCookie, errInvalidAccessKey + } + + headerKey := r.Header.Get(accessKeyHeaderName) + if headerKey == key { + return accessKeySourceHeader, nil + } else if headerKey != "" { + return accessKeySourceHeader, errInvalidAccessKey + } + + formKey := r.PostFormValue(accessKeyParamName) + if formKey == key { + return accessKeySourceForm, nil + } else if formKey != "" { + return accessKeySourceForm, errInvalidAccessKey + } + + queryKey := r.URL.Query().Get(accessKeyParamName) + if queryKey == key { + return accessKeySourceQuery, nil + } else if formKey != "" { + return accessKeySourceQuery, errInvalidAccessKey + } + + return accessKeySourceNone, errInvalidAccessKey +} + +func setAccessKeyCookies(w http.ResponseWriter, domain, fileName, value string, expires time.Time) { + cookie := http.Cookie{ + Name: accessKeyHeaderName, + Value: value, + HttpOnly: true, + Domain: domain, + Expires: expires, + } + + cookie.Path = Config.sitePath + fileName + http.SetCookie(w, &cookie) + + cookie.Path = Config.sitePath + Config.selifPath + fileName + http.SetCookie(w, &cookie) +} + +func fileAccessHandler(c web.C, w http.ResponseWriter, r *http.Request) { + if !Config.noDirectAgents && cliUserAgentRe.MatchString(r.Header.Get("User-Agent")) && !strings.EqualFold("application/json", r.Header.Get("Accept")) { + fileServeHandler(c, w, r) + return + } + + fileName := c.URLParams["name"] + + metadata, err := checkFile(fileName) + if err == backends.NotFoundErr { + notFoundHandler(c, w, r) + return + } else if err != nil { + oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") + return + } + + if src, err := checkAccessKey(r, &metadata); err != nil { + // remove invalid cookie + if src == accessKeySourceCookie { + setAccessKeyCookies(w, getSiteURL(r), fileName, "", time.Unix(0, 0)) + } + + if strings.EqualFold("application/json", r.Header.Get("Accept")) { + dec := json.NewEncoder(w) + _ = dec.Encode(map[string]string{ + "error": errInvalidAccessKey.Error(), + }) + + return + } + + _ = renderTemplate(Templates["access.html"], pongo2.Context{ + "filename": fileName, + "accesspath": fileName, + }, r, w) + + return + } + + if metadata.AccessKey != "" { + var expiry time.Time + if Config.accessKeyCookieExpiry != 0 { + expiry = time.Now().Add(time.Duration(Config.accessKeyCookieExpiry) * time.Second) + } + setAccessKeyCookies(w, getSiteURL(r), fileName, metadata.AccessKey, expiry) + } + + fileDisplayHandler(c, w, r, fileName, metadata) +} diff --git a/backends/localfs/localfs.go b/backends/localfs/localfs.go index 47187b6..42e32b8 100644 --- a/backends/localfs/localfs.go +++ b/backends/localfs/localfs.go @@ -19,6 +19,7 @@ type LocalfsBackend struct { type MetadataJSON struct { DeleteKey string `json:"delete_key"` + AccessKey string `json:"access_key,omitempty"` Sha256sum string `json:"sha256sum"` Mimetype string `json:"mimetype"` Size int64 `json:"size"` @@ -57,6 +58,7 @@ func (b LocalfsBackend) Head(key string) (metadata backends.Metadata, err error) } metadata.DeleteKey = mjson.DeleteKey + metadata.AccessKey = mjson.AccessKey metadata.Mimetype = mjson.Mimetype metadata.ArchiveFiles = mjson.ArchiveFiles metadata.Sha256sum = mjson.Sha256sum @@ -84,12 +86,13 @@ func (b LocalfsBackend) writeMetadata(key string, metadata backends.Metadata) er metaPath := path.Join(b.metaPath, key) mjson := MetadataJSON{ - DeleteKey: metadata.DeleteKey, - Mimetype: metadata.Mimetype, + DeleteKey: metadata.DeleteKey, + AccessKey: metadata.AccessKey, + Mimetype: metadata.Mimetype, ArchiveFiles: metadata.ArchiveFiles, - Sha256sum: metadata.Sha256sum, - Expiry: metadata.Expiry.Unix(), - Size: metadata.Size, + Sha256sum: metadata.Sha256sum, + Expiry: metadata.Expiry.Unix(), + Size: metadata.Size, } dst, err := os.Create(metaPath) @@ -108,7 +111,7 @@ func (b LocalfsBackend) writeMetadata(key string, metadata backends.Metadata) er return nil } -func (b LocalfsBackend) Put(key string, r io.Reader, expiry time.Time, deleteKey string) (m backends.Metadata, err error) { +func (b LocalfsBackend) Put(key string, r io.Reader, expiry time.Time, deleteKey, accessKey string) (m backends.Metadata, err error) { filePath := path.Join(b.filesPath, key) dst, err := os.Create(filePath) @@ -126,16 +129,17 @@ func (b LocalfsBackend) Put(key string, r io.Reader, expiry time.Time, deleteKey return m, err } - dst.Seek(0 ,0) + dst.Seek(0, 0) m, err = helpers.GenerateMetadata(dst) if err != nil { os.Remove(filePath) return } - dst.Seek(0 ,0) + dst.Seek(0, 0) m.Expiry = expiry m.DeleteKey = deleteKey + m.AccessKey = accessKey m.ArchiveFiles, _ = helpers.ListArchiveFiles(m.Mimetype, m.Size, dst) err = b.writeMetadata(key, m) diff --git a/backends/meta.go b/backends/meta.go index 7ba522d..b22276e 100644 --- a/backends/meta.go +++ b/backends/meta.go @@ -7,6 +7,7 @@ import ( type Metadata struct { DeleteKey string + AccessKey string Sha256sum string Mimetype string Size int64 diff --git a/backends/s3/s3.go b/backends/s3/s3.go index fc2a1b0..afdabf0 100644 --- a/backends/s3/s3.go +++ b/backends/s3/s3.go @@ -86,6 +86,7 @@ func mapMetadata(m backends.Metadata) map[string]*string { "Size": aws.String(strconv.FormatInt(m.Size, 10)), "Mimetype": aws.String(m.Mimetype), "Sha256sum": aws.String(m.Sha256sum), + "AccessKey": aws.String(m.AccessKey), } } @@ -104,10 +105,15 @@ func unmapMetadata(input map[string]*string) (m backends.Metadata, err error) { m.DeleteKey = aws.StringValue(input["Delete_key"]) m.Mimetype = aws.StringValue(input["Mimetype"]) m.Sha256sum = aws.StringValue(input["Sha256sum"]) + + if key, ok := input["AccessKey"]; ok { + m.AccessKey = aws.StringValue(key) + } + return } -func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey string) (m backends.Metadata, err error) { +func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey, accessKey string) (m backends.Metadata, err error) { tmpDst, err := ioutil.TempFile("", "linx-server-upload") if err != nil { return m, err @@ -133,6 +139,7 @@ func (b S3Backend) Put(key string, r io.Reader, expiry time.Time, deleteKey stri } m.Expiry = expiry m.DeleteKey = deleteKey + m.AccessKey = accessKey // XXX: we may not be able to write this to AWS easily //m.ArchiveFiles, _ = helpers.ListArchiveFiles(m.Mimetype, m.Size, tmpDst) diff --git a/backends/storage.go b/backends/storage.go index fdd8cd6..5d973c4 100644 --- a/backends/storage.go +++ b/backends/storage.go @@ -11,7 +11,7 @@ type StorageBackend interface { Exists(key string) (bool, error) Head(key string) (Metadata, error) Get(key string) (Metadata, io.ReadCloser, error) - Put(key string, r io.Reader, expiry time.Time, deleteKey string) (Metadata, error) + Put(key string, r io.Reader, expiry time.Time, deleteKey, accessKey string) (Metadata, error) PutMetadata(key string, m Metadata) error Size(key string) (int64, error) } diff --git a/display.go b/display.go index feb16da..e15b1b6 100644 --- a/display.go +++ b/display.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "net/http" "path/filepath" - "regexp" "strconv" "strings" "time" @@ -21,24 +20,7 @@ import ( const maxDisplayFileSizeBytes = 1024 * 512 -var cliUserAgentRe = regexp.MustCompile("(?i)(lib)?curl|wget") - -func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { - if !Config.noDirectAgents && cliUserAgentRe.MatchString(r.Header.Get("User-Agent")) && !strings.EqualFold("application/json", r.Header.Get("Accept")) { - fileServeHandler(c, w, r) - return - } - - fileName := c.URLParams["name"] - - metadata, err := checkFile(fileName) - if err == backends.NotFoundErr { - notFoundHandler(c, w, r) - return - } else if err != nil { - oopsHandler(c, w, r, RespAUTO, "Corrupt metadata.") - return - } +func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request, fileName string, metadata backends.Metadata) { var expiryHuman string if metadata.Expiry != expiry.NeverExpire { expiryHuman = humanize.RelTime(time.Now(), metadata.Expiry, "", "") @@ -130,7 +112,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request) { tpl = Templates["display/file.html"] } - err = renderTemplate(tpl, pongo2.Context{ + err := renderTemplate(tpl, pongo2.Context{ "mime": metadata.Mimetype, "filename": fileName, "size": sizeHuman, diff --git a/fileserve.go b/fileserve.go index 202e477..27a28a9 100644 --- a/fileserve.go +++ b/fileserve.go @@ -27,6 +27,16 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { return } + if src, err := checkAccessKey(r, &metadata); err != nil { + // remove invalid cookie + if src == accessKeySourceCookie { + setAccessKeyCookies(w, getSiteURL(r), fileName, "", time.Unix(0, 0)) + } + unauthorizedHandler(c, w, r) + + return + } + if !Config.allowHotlink { referer := r.Header.Get("Referer") u, _ := url.Parse(referer) diff --git a/server.go b/server.go index 71a9c4d..50c4465 100644 --- a/server.go +++ b/server.go @@ -15,7 +15,7 @@ import ( "syscall" "time" - "github.com/GeertJohan/go.rice" + rice "github.com/GeertJohan/go.rice" "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/backends/localfs" "github.com/andreimarcu/linx-server/backends/s3" @@ -68,6 +68,7 @@ var Config struct { s3Bucket string s3ForcePathStyle bool forceRandomFilename bool + accessKeyCookieExpiry uint64 } var Templates = make(map[string]*pongo2.Template) @@ -200,7 +201,8 @@ func setup() *web.Mux { mux.Get(Config.sitePath+"static/*", staticHandler) mux.Get(Config.sitePath+"favicon.ico", staticHandler) mux.Get(Config.sitePath+"robots.txt", staticHandler) - mux.Get(nameRe, fileDisplayHandler) + mux.Get(nameRe, fileAccessHandler) + mux.Post(nameRe, fileAccessHandler) mux.Get(selifRe, fileServeHandler) mux.Get(selifIndexRe, unauthorizedHandler) mux.Get(torrentRe, fileTorrentHandler) @@ -273,6 +275,7 @@ func main() { "Force path-style addressing for S3 (e.g. https://s3.amazonaws.com/linx/example.txt)") 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)") iniflags.Parse() diff --git a/templates.go b/templates.go index 79c90ce..acd9980 100644 --- a/templates.go +++ b/templates.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "github.com/GeertJohan/go.rice" + rice "github.com/GeertJohan/go.rice" "github.com/flosch/pongo2" ) @@ -51,6 +51,7 @@ func populateTemplatesMap(tSet *pongo2.TemplateSet, tMap map[string]*pongo2.Temp "401.html", "404.html", "oops.html", + "access.html", "display/audio.html", "display/image.html", diff --git a/templates/access.html b/templates/access.html new file mode 100644 index 0000000..be3ada9 --- /dev/null +++ b/templates/access.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block content %} +
+ + {{ filename }} is protected with password
+ + + +
+{% endblock %} diff --git a/templates/index.html b/templates/index.html index 2843109..30b281b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -27,6 +27,7 @@
+
diff --git a/upload.go b/upload.go index 6533bfe..254e0eb 100644 --- a/upload.go +++ b/upload.go @@ -40,6 +40,7 @@ type UploadRequest struct { expiry time.Duration // Seconds until expiry, 0 = never deleteKey string // Empty string if not defined randomBarename bool + accessKey string // Empty string if not defined } // Metadata associated with a file as it would actually be stored @@ -88,6 +89,7 @@ func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { } upReq.expiry = parseExpiry(r.PostFormValue("expires")) + upReq.accessKey = r.PostFormValue(accessKeyParamName) if r.PostFormValue("randomize") == "true" { upReq.randomBarename = true @@ -181,6 +183,7 @@ func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) { upReq.filename = filepath.Base(grabUrl.Path) upReq.src = http.MaxBytesReader(w, resp.Body, Config.maxSize) upReq.deleteKey = r.FormValue("deletekey") + upReq.accessKey = r.FormValue(accessKeyParamName) upReq.randomBarename = r.FormValue("randomize") == "yes" upReq.expiry = parseExpiry(r.FormValue("expiry")) @@ -211,6 +214,7 @@ func uploadHeaderProcess(r *http.Request, upReq *UploadRequest) { } upReq.deleteKey = r.Header.Get("Linx-Delete-Key") + upReq.accessKey = r.Header.Get(accessKeyHeaderName) // Get seconds until expiry. Non-integer responses never expire. expStr := r.Header.Get("Linx-Expiry") @@ -310,7 +314,7 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { upReq.deleteKey = uniuri.NewLen(30) } - upload.Metadata, err = storageBackend.Put(upload.Filename, io.MultiReader(bytes.NewReader(header), upReq.src), fileExpiry, upReq.deleteKey) + upload.Metadata, err = storageBackend.Put(upload.Filename, io.MultiReader(bytes.NewReader(header), upReq.src), fileExpiry, upReq.deleteKey, upReq.accessKey) if err != nil { return upload, err } @@ -328,6 +332,7 @@ func generateJSONresponse(upload Upload, r *http.Request) []byte { "direct_url": getSiteURL(r) + Config.selifPath + upload.Filename, "filename": upload.Filename, "delete_key": upload.Metadata.DeleteKey, + "access_key": upload.Metadata.AccessKey, "expiry": strconv.FormatInt(upload.Metadata.Expiry.Unix(), 10), "size": strconv.FormatInt(upload.Metadata.Size, 10), "mimetype": upload.Metadata.Mimetype, From 4361c44128f3a2312df32c8d6b20a2f4c432ec79 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Sun, 1 Mar 2020 03:23:54 +0300 Subject: [PATCH 078/233] fix domain field in setAccessKeyCookies --- access.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/access.go b/access.go index bacea24..bf32013 100644 --- a/access.go +++ b/access.go @@ -3,7 +3,10 @@ package main import ( "encoding/json" "errors" + "log" "net/http" + "net/url" + "path" "regexp" "strings" "time" @@ -70,19 +73,25 @@ func checkAccessKey(r *http.Request, metadata *backends.Metadata) (accessKeySour return accessKeySourceNone, errInvalidAccessKey } -func setAccessKeyCookies(w http.ResponseWriter, domain, fileName, value string, expires time.Time) { +func setAccessKeyCookies(w http.ResponseWriter, siteURL, fileName, value string, expires time.Time) { + u, err := url.Parse(siteURL) + if err != nil { + log.Printf("cant parse siteURL (%v): %v", siteURL, err) + return + } + cookie := http.Cookie{ Name: accessKeyHeaderName, Value: value, HttpOnly: true, - Domain: domain, + Domain: u.Hostname(), Expires: expires, } - cookie.Path = Config.sitePath + fileName + cookie.Path = path.Join(u.Path, fileName) http.SetCookie(w, &cookie) - cookie.Path = Config.sitePath + Config.selifPath + fileName + cookie.Path = path.Join(u.Path, Config.selifPath, fileName) http.SetCookie(w, &cookie) } From 5a6e589500f5232e9c1b91fc57576e65adc70e05 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Sun, 1 Mar 2020 03:43:53 +0300 Subject: [PATCH 079/233] redesign access key input box in uploader --- static/css/linx.css | 18 ++++++++++++++++++ templates/index.html | 7 ++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/static/css/linx.css b/static/css/linx.css index 646c83b..4d7bc7c 100644 --- a/static/css/linx.css +++ b/static/css/linx.css @@ -264,6 +264,24 @@ body { margin: 0; } +#access_key_checkbox { + margin: 0; +} + +#access_key_checkbox:checked ~ #access_key { + display: inline-block; +} +#access_key_checkbox:checked ~ #access_key_label { + display: none; +} + +#access_key { + line-height: 1em; + padding: 1px; + border: 3px; + display: none; +} + .oopscontent { width: 400px; } diff --git a/templates/index.html b/templates/index.html index 30b281b..616e1a6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -27,7 +27,12 @@
- + +
+ + + +
From 3947197225ac6b6f15d0b4c70b2da862f3a49593 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Sun, 1 Mar 2020 03:47:28 +0300 Subject: [PATCH 080/233] wrap access.html in white container --- templates/access.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/access.html b/templates/access.html index be3ada9..4fd65cc 100644 --- a/templates/access.html +++ b/templates/access.html @@ -1,10 +1,10 @@ {% extends "base.html" %} {% block content %} -
+
{{ filename }} is protected with password
- +
From 4baaa0a15f6044491ed9999e17273e923caea5a4 Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Sun, 1 Mar 2020 03:56:43 +0300 Subject: [PATCH 081/233] add access_key to paste --- templates/paste.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/templates/paste.html b/templates/paste.html index 245b7a3..eead748 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -8,6 +8,10 @@ {% if not forcerandom %}{% endif %}.
+ + + +
diff --git a/upload.go b/upload.go index 6533bfe..70ff08b 100644 --- a/upload.go +++ b/upload.go @@ -157,9 +157,20 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) { if Config.remoteAuthFile != "" { - result, err := checkAuth(remoteAuthKeys, r.FormValue("key")) + key := r.FormValue("key") + if key == "" && Config.basicAuth { + _, password, ok := r.BasicAuth() + if ok { + key = password + } + } + result, err := checkAuth(remoteAuthKeys, key) if err != nil || !result { - unauthorizedHandler(c, w, r) + if Config.basicAuth { + badAuthorizationHandler(w, r) + } else { + unauthorizedHandler(c, w, r) + } return } } From 779fc5c42592f8a9aff246a0c48f9c7fd677bdd9 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Fri, 6 Mar 2020 15:26:10 -0800 Subject: [PATCH 086/233] Readme: Clarify basic auth usage --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 144dd40..8564e01 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ allowhotlink = true #### 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 - ```-remoteauthfile path/to/remoteauthfile``` -- (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys -- ```-basicauth``` -- (optionally) allow basic authorization to upload or paste files from browser when `-authfile` is enabled +- ```-basicauth``` -- (optionally) allow basic authorization to upload or paste files from browser when `-authfile` is enabled. When uploading, you will be prompted to enter a user and password - leave the user blank and use your auth key as the password A helper utility ```linx-genkey``` is provided which hashes keys to the format required in the auth files. From e06e9ad6fe73cf8aa8c14cfb301fecaa6947e7cc Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Sat, 7 Mar 2020 17:52:07 -0800 Subject: [PATCH 087/233] Access key: Some cosmetic changes --- README.md | 2 +- static/css/linx.css | 19 +++++++++++++------ static/js/upload.js | 36 +++++++++++++++++++++--------------- templates/API.html | 2 +- templates/access.html | 5 +++-- templates/index.html | 28 +++++++++++++++++----------- 6 files changed, 56 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 8564e01..84da53d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You can see what it looks like using the demo: [https://demo.linx-server.net/](h - Display syntax-highlighted code with in-place editing - Documented API with keys if need to restrict uploads (can use [linx-client](https://github.com/andreimarcu/linx-client) for uploading through command-line) - Torrent download of files using web seeding -- File expiry, deletion key, and random filename options +- File expiry, deletion key, file access key, and random filename options ### Screenshots diff --git a/static/css/linx.css b/static/css/linx.css index 4d7bc7c..edb88c4 100644 --- a/static/css/linx.css +++ b/static/css/linx.css @@ -264,21 +264,28 @@ body { margin: 0; } +#access_key { + min-width: 100%; + line-height: 1.3em; +} + +#access_key input, span { + vertical-align: middle; +} + #access_key_checkbox { margin: 0; } -#access_key_checkbox:checked ~ #access_key { +#access_key_checkbox:checked ~ #access_key_input { display: inline-block; } -#access_key_checkbox:checked ~ #access_key_label { +#access_key_checkbox:checked ~ #access_key_text { display: none; } -#access_key { - line-height: 1em; - padding: 1px; - border: 3px; +#access_key_input { + padding: 0; display: none; } diff --git a/static/js/upload.js b/static/js/upload.js index fce6e77..c5ac28a 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -1,15 +1,15 @@ // @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later Dropzone.options.dropzone = { - init: function() { + init: function () { var dzone = document.getElementById("dzone"); dzone.style.display = "block"; }, - addedfile: function(file) { + addedfile: function (file) { if (!this.options.autoProcessQueue) { var dropzone = this; var xhr = new XMLHttpRequest(); - xhr.onload = function() { + xhr.onload = function () { if (xhr.readyState !== XMLHttpRequest.DONE) { return; } @@ -39,7 +39,7 @@ Dropzone.options.dropzone = { var cancelAction = document.createElement("span"); cancelAction.className = "cancel"; cancelAction.innerHTML = "Cancel"; - cancelAction.addEventListener('click', function(ev) { + cancelAction.addEventListener('click', function (ev) { this.removeFile(file); }.bind(this)); file.cancelActionElement = cancelAction; @@ -53,19 +53,19 @@ Dropzone.options.dropzone = { document.getElementById("uploads").appendChild(upload); }, - uploadprogress: function(file, p, bytesSent) { + uploadprogress: function (file, p, bytesSent) { p = parseInt(p); file.progressElement.innerHTML = p + "%"; file.uploadElement.setAttribute("style", 'background-image: -webkit-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -moz-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -ms-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: -o-linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%); background-image: linear-gradient(left, #F2F4F7 ' + p + '%, #E2E2E2 ' + p + '%)'); }, - sending: function(file, xhr, formData) { + sending: function (file, xhr, formData) { var randomize = document.getElementById("randomize"); - if(randomize != null) { + if (randomize != null) { formData.append("randomize", randomize.checked); } formData.append("expires", document.getElementById("expires").value); }, - success: function(file, resp) { + success: function (file, resp) { file.fileActions.removeChild(file.progressElement); var fileLabelLink = document.createElement("a"); @@ -79,11 +79,11 @@ Dropzone.options.dropzone = { var deleteAction = document.createElement("span"); deleteAction.innerHTML = "Delete"; deleteAction.className = "cancel"; - deleteAction.addEventListener('click', function(ev) { + deleteAction.addEventListener('click', function (ev) { xhr = new XMLHttpRequest(); xhr.open("DELETE", resp.url, true); xhr.setRequestHeader("Linx-Delete-Key", resp.delete_key); - xhr.onreadystatechange = function(file) { + xhr.onreadystatechange = function (file) { if (xhr.readyState == 4 && xhr.status === 200) { var text = document.createTextNode("Deleted "); file.fileLabel.insertBefore(text, file.fileLabelLink); @@ -97,15 +97,15 @@ Dropzone.options.dropzone = { file.cancelActionElement = deleteAction; file.fileActions.appendChild(deleteAction); }, - canceled: function(file) { + canceled: function (file) { this.options.error(file); }, - error: function(file, resp, xhrO) { + error: function (file, resp, xhrO) { file.fileActions.removeChild(file.cancelActionElement); file.fileActions.removeChild(file.progressElement); if (file.status === "canceled") { - file.fileLabel.innerHTML = file.name + ": Canceled "; + file.fileLabel.innerHTML = file.name + ": Canceled "; } else { if (resp.error) { @@ -125,12 +125,12 @@ Dropzone.options.dropzone = { maxFilesize: Math.round(parseInt(document.getElementById("dropzone").getAttribute("data-maxsize"), 10) / 1024 / 1024), previewsContainer: "#uploads", parallelUploads: 5, - headers: {"Accept": "application/json"}, + headers: { "Accept": "application/json" }, dictDefaultMessage: "Click or Drop file(s) or Paste image", dictFallbackMessage: "" }; -document.onpaste = function(event) { +document.onpaste = function (event) { var items = (event.clipboardData || event.originalEvent.clipboardData).items; for (index in items) { var item = items[index]; @@ -140,4 +140,10 @@ document.onpaste = function(event) { } }; +document.getElementById("access_key_checkbox").onchange = function (event) { + if (event.target.checked == false) { + document.getElementById("access_key_input").value = ""; + } +}; + // @end-license diff --git a/templates/API.html b/templates/API.html index 0a395f2..11d9ab1 100644 --- a/templates/API.html +++ b/templates/API.html @@ -49,7 +49,7 @@ “direct_url”: the url to access the file directly
“filename”: the (optionally generated) filename
“delete_key”: the (optionally generated) deletion key,
- “access_key”: the (optionally generated) access key,
+ “access_key”: the (optionally supplied) access key,
“expiry”: the unix timestamp at which the file will expire (0 if never)
“size”: the size in bytes of the file
“mimetype”: the guessed mimetype of the file
diff --git a/templates/access.html b/templates/access.html index 4fd65cc..c61cf51 100644 --- a/templates/access.html +++ b/templates/access.html @@ -1,11 +1,12 @@ {% extends "base.html" %} {% block content %} -
+
- {{ filename }} is protected with password
+ {{ filename }} is protected with a password:

+

{% endblock %} diff --git a/templates/index.html b/templates/index.html index 132a488..9e73457 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,21 +17,27 @@
- + + + +
- -
- - - +
+ + +
From 7ca33310447b7cb176f707c217c33fbc23cebb47 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Sat, 7 Mar 2020 18:52:29 -0800 Subject: [PATCH 088/233] Access Key: Fix mobile access --- static/css/linx.css | 7 ------- static/js/upload.js | 7 ++++++- templates/index.html | 23 ++++++++++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/static/css/linx.css b/static/css/linx.css index edb88c4..e47f48f 100644 --- a/static/css/linx.css +++ b/static/css/linx.css @@ -277,13 +277,6 @@ body { margin: 0; } -#access_key_checkbox:checked ~ #access_key_input { - display: inline-block; -} -#access_key_checkbox:checked ~ #access_key_text { - display: none; -} - #access_key_input { padding: 0; display: none; diff --git a/static/js/upload.js b/static/js/upload.js index c5ac28a..db2010c 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -141,8 +141,13 @@ document.onpaste = function (event) { }; document.getElementById("access_key_checkbox").onchange = function (event) { - if (event.target.checked == false) { + if (event.target.checked) { + document.getElementById("access_key_input").style.display = "inline-block"; + document.getElementById("access_key_text").style.display = "none"; + } else { document.getElementById("access_key_input").value = ""; + document.getElementById("access_key_input").style.display = "none"; + document.getElementById("access_key_text").style.display = "inline-block"; } }; diff --git a/templates/index.html b/templates/index.html index 9e73457..34e5d86 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,9 +4,10 @@ {% endblock %} -{% block content %} +{% block content %}
-
+

@@ -17,26 +18,30 @@
- - + +
- + +
@@ -48,4 +53,4 @@ -{% endblock %} +{% endblock %} \ No newline at end of file From 773eb877bd02a6678599737e10c826775ecba128 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Tue, 10 Mar 2020 22:45:24 -0700 Subject: [PATCH 089/233] Basic auth: Fix #201 - broken uploads --- auth.go | 2 +- server.go | 6 ++---- static/js/upload.js | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/auth.go b/auth.go index 463dddb..3dc5ba6 100644 --- a/auth.go +++ b/auth.go @@ -115,7 +115,7 @@ func badAuthorizationHandler(w http.ResponseWriter, r *http.Request) { if Config.siteName != "" { rs = fmt.Sprintf(` realm="%s"`, Config.siteName) } - w.Header().Set("WWW-Authenticate", `Basic` + rs) + w.Header().Set("WWW-Authenticate", `Basic`+rs) } http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) } diff --git a/server.go b/server.go index 907b719..661c2ea 100644 --- a/server.go +++ b/server.go @@ -196,20 +196,18 @@ func setup() *web.Mux { AuthFile: Config.authFile, UnauthMethods: []string{}, } - okFunc := func (w http.ResponseWriter, r *http.Request) { + okFunc := func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", Config.sitePath) w.WriteHeader(http.StatusFound) } - authHandler := auth { + authHandler := auth{ successHandler: http.HandlerFunc(okFunc), failureHandler: http.HandlerFunc(badAuthorizationHandler), authKeys: readAuthKeys(Config.authFile), o: options, } mux.Head(Config.sitePath+"auth", authHandler) - mux.Head(Config.sitePath+"auth/", authHandler) mux.Get(Config.sitePath+"auth", authHandler) - mux.Get(Config.sitePath+"auth/", authHandler) } mux.Post(Config.sitePath+"upload", uploadPostHandler) diff --git a/static/js/upload.js b/static/js/upload.js index db2010c..7ad8da6 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -20,7 +20,7 @@ Dropzone.options.dropzone = { dropzone.cancelUpload(file) } }; - xhr.open("HEAD", "auth/", true); + xhr.open("HEAD", "/auth", true); xhr.send() } var upload = document.createElement("div"); From 9a8bb1bb7fbe2ec845a4ba9802cead802b2d824d Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 8 Mar 2020 15:17:37 +0100 Subject: [PATCH 090/233] Add support for go modules --- go.mod | 19 +++++++++++++++ go.sum | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0c3a2b7 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/andreimarcu/linx-server + +go 1.13 + +require ( + github.com/GeertJohan/go.rice v1.0.0 + github.com/aws/aws-sdk-go v1.29.19 + github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 + github.com/dustin/go-humanize v1.0.0 + github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 + github.com/microcosm-cc/bluemonday v1.0.2 + github.com/minio/sha256-simd v0.1.1 + github.com/russross/blackfriday v1.5.1 + github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de + github.com/zeebo/bencode v1.0.0 + github.com/zenazn/goji v0.9.0 + golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 + gopkg.in/h2non/filetype.v1 v1.0.5 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..15a736c --- /dev/null +++ b/go.sum @@ -0,0 +1,76 @@ +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/aws/aws-sdk-go v1.29.19 h1:+jifYixffn6kzWygtGWFWQMv0tDGyISZHNwugF9V2sE= +github.com/aws/aws-sdk-go v1.29.19/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= +github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= +github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 h1:GY1+t5Dr9OKADM64SYnQjw/w99HMYvQ0A8/JoUkxVmc= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.1 h1:B8ZN6pD4PVofmlDCDUdELeYrbsVIDM/bpjW3v3zgcRc= +github.com/russross/blackfriday v1.5.1/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de h1:fkw+7JkxF3U1GzQoX9h69Wvtvxajo5Rbzy6+YMMzPIg= +github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de/go.mod h1:irMhzlTz8+fVFj6CH2AN2i+WI5S6wWFtK3MBCIxIpyI= +github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA= +github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y= +github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/h2non/filetype.v1 v1.0.5 h1:CC1jjJjoEhNVbMhXYalmGBhOBK2V70Q1N850wt/98/Y= +gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 68a80d36983c265fba45a83b463b6603889abd72 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Wed, 11 Mar 2020 02:00:37 -0700 Subject: [PATCH 091/233] Go mod: update to 1.14 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0c3a2b7..50a48ab 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/andreimarcu/linx-server -go 1.13 +go 1.14 require ( github.com/GeertJohan/go.rice v1.0.0 From a4c0e0ac37da12a29d322a5c53a581e6bb3dd7a8 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Wed, 11 Mar 2020 02:01:38 -0700 Subject: [PATCH 092/233] .gitignore: Add ignore entries --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 37e1e54..25b0cd6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ _cgo_defun.c _cgo_gotypes.go _cgo_export.* +.DS_Store + _testmain.go *.exe @@ -27,7 +29,9 @@ _testmain.go *.prof linx-server +linx-cleanup +linx-genkey files/ meta/ binaries/ -linx-cleanup +authfile From 9f6346a373dba25c4570f8e7dcb8f79f11ac964e Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 01:20:56 -0700 Subject: [PATCH 093/233] Basic auth: fix for using different paths --- static/js/upload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/upload.js b/static/js/upload.js index 7ad8da6..c39edc7 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -20,7 +20,7 @@ Dropzone.options.dropzone = { dropzone.cancelUpload(file) } }; - xhr.open("HEAD", "/auth", true); + xhr.open("HEAD", "auth", true); xhr.send() } var upload = document.createElement("div"); From fb0d44f132d64a7cc68fda0fd9020a4e3e4348f7 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 13:32:35 -0700 Subject: [PATCH 094/233] Custom pages: Implement adding custom markdown pages to the site navigation --- .gitignore | 1 + README.md | 1 + custom_pages.go | 40 ++++++++++++++++++ pages.go | 15 +++++++ server.go | 13 ++++++ static/images/404.jpg | Bin 18323 -> 19742 bytes templates.go | 3 ++ templates/404.html | 4 +- templates/API.html | 82 ++++++++++++++++++++---------------- templates/access.html | 4 +- templates/base.html | 13 ++++-- templates/custom_page.html | 19 +++++++++ templates/display/base.html | 50 +++++++++++----------- templates/paste.html | 53 +++++++++++++---------- 14 files changed, 207 insertions(+), 91 deletions(-) create mode 100644 custom_pages.go create mode 100644 templates/custom_page.html diff --git a/.gitignore b/.gitignore index 25b0cd6..c6750eb 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ linx-genkey files/ meta/ binaries/ +custom_pages/ authfile diff --git a/README.md b/README.md index 84da53d..98bf585 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/custom_pages.go b/custom_pages.go new file mode 100644 index 0000000..105a04a --- /dev/null +++ b/custom_pages.go @@ -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, "_", " ") + } + } +} diff --git a/pages.go b/pages.go index 6fcc934..ae8de42 100644 --- a/pages.go +++ b/pages.go @@ -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) diff --git a/server.go b/server.go index 661c2ea..e1f0c0a 100644 --- a/server.go +++ b/server.go @@ -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() diff --git a/static/images/404.jpg b/static/images/404.jpg index 16ed400d7844ca4485db66802be08e112162f184..57bf16f0bb2a590f15c9c77c7baf5c660b0b2e0e 100644 GIT binary patch literal 19742 zcmc$_Q*dv=*Dd;sZQFLTW81cE+qP{dJGSj)$2NCtbH^wD@1C#jdAN1zJl)glVb!W$ z)ivkp9$jOO9_xGUdl!HxB_=5b00II4fPOB(_a-3Z=js1)`yK=!K?3OjJ%Ruc1Avi$ zK#+jGhXDjX0RjU9f&GN}-whH90va3$7z7jy=6eGG`JXfZfWN!{-Ti;@4!^StKyqui zq1Ze-6W>wJ7Wjz;NY&HK=LGnyjtjdL7H3 z>sduKx%myW*>9K?5}^gw@c=7gt!2IS~Kz9X&o z20f5KP0NhZHqk*%q4~=1S@#+M_yv)u+uVWM;9}HFvr&8HU_;+uB&#FOyY)oRE zWO2WMBLAfXU)`L#Tx@P{0RY@IM!k+#%!73;G}Jf;d$EC>s#3>M44)33UuqM2vl&3> zG+axE6mq!}$*o!2ja-#2aj6V8pZ!2*){eLwUx>k(?w03LzbTcIY{aOIE3|DArH$T8 z0AS!gl`-#F-++l_C2fp!o)#q#- z?RnNMRzth|MYw?g_>@ffxUsIZGjTJf_|cQW%uyQvsOg-M-0>voygj=) zRJs}F{YR5~SYL1B(}vCKHUdwyL(d8n8N2piS2{oqJP$c@2?0AF8D?1!+zb z<y++6DiCv#X7!A+LK4DW;fQZoE1%TF-ayf#&Y|GS_d{=d@#1_A*E zhkyk8ue3n^PtvL})&c;q&0Wd|eb8op#@twu2qk(oV=I zyyK`(FT2x~Ij{Ei0gXibtb#syhNJ!eU1k9PpVU|oe|AP-02nYRC@92#y$u2i3q_qpw zaynDBGFOl_8$yenhOPf%-1;yx-TK#q;)ZOd>0+p33*(U&^zDRjN}sNyr>0QOPO}+J zkY9z)24=&`sVG@=+gmdKSPMsc+~~O>=M%e1A8KBK_DVBp8&Px<)*p}I;v6&1bXs%H zS7X<|idr=#mU-7}#kG*-X#F-%jn)g*zy6GyZ1pPqYcPt+JY1|~=#OF%B8A;m{3NLb z4`pPe#Fe&chQ_7^Z^onxRYDK$&sVV#mEzi%tLHib?Jjo-Sy9vIR8-6I)-P=sJKQIt z7Z+6^jn?}mjD{?00tAMFD$sXfldo5UUX7hDbU>n=J<{EIk)#j z&;5HcT7%k@&L7nkuD^5OuFgh$#+q$=Dc#1XHHDs4N_rA7LyaVQE}X*nQ-uj8Kc8J;u6*8Y7Yi>kmY=LO9 zM6<>WS&SLyNqik=zd|Autq|Hft7x$wwpgb0$CyTyDR83ZvEwIu%80s6J92Rv|^k))h*nknr=f>tCM(v@q}}>vmy?#2W^F6A zZS{fC5!oLQcGhUN8tVH^&oJp4jV5z_a_lK5*}jQqh!4v+Ty;)U@w)wybz+yP>%V$2 zcA3-=AnIigPYqS``-G_gtYVVT zR~Q{vvoB5fS7BO%=C>x8zjWg^N(E`u9@7r~cs0ZvT)lC(_WWIfK{n z^!)03m>?a0anQwSPRYd-CQ9jYHWsUyj5H-8*{^s`+5ox1s&#&-l91WJgOP}q^N<

R8b@c4o$dtO37lR2I7{&OxG~deR3^w2ti6)5OofjQFQml`CpFah$sw;_A_M z9xv6|6630vb~22vY2n)B-dt}jw?4_HMOzvC>L#m)xKxr$o1Ie4ET_3uhP@OjVDa7p zRHF-XOp8V^3WW%_()5$pBpi06%^t6*Vp@Reve42lS(Ryd3wgxF#J4MZ10}9#z$IDd zZmT9EK{^0V_{6@RO~8gu-srfUoGPoV!6eH-3(Y&zR+&js0p)=87&cs;|93Z^ld{<5i^3u0;OJQ1!a1d)Hs>5_ zeWLG>VYkXvuND_%4Q@?ZJ#@*M@lkbv3*V#Yr+9#C;5ho>IC-|ELnGCSp|Hk)ykTu@AEop1F+<&2zrKN8OT~!g1JW9>x==V+ zI^frQ5+S6V=7>3K*Vr}gSYy^rl~owtkh7z^zAlEcTFDPxprT}Kn$s&4j<{E4PJtye zJ;C_cEyiST>-?2$894tu!&j|o`FZUz41Ah3XU12=0FgL*`&7#^!FG2BUdz{wJkZ#a zgk%yNGs~#M+~8T41mgxl{gKb}*EZ}yU91=u8bkuGm4ypENRU7mzSF1Jo>_1>QzdymrT%N04K^6^{M>ua7-zLk7)Z+Ek!z}bnavyH8lg}TFa z7O>Im*r-L!X{RdX8ZP3F&z<9iN%T%DmiT1F$Q3SY92wRSTUp{0&Oc1EoWl;@B@lUQ zUfFcX&sa*H^g`9YxYn01C?Ok88U8Hl-$>JvEn;Pg1=1%2>h}lKY_&A8mnAMr7H^b3 zM4har)sM6nqbKIrn%J#En9$U%G%`swB|_3nsVA)Q2KwM=kGv|QS}LkmYL)ZNFYKd! zZT}LhJT^&f)plYnu9S+5xGT4^BOb7|{gOqFn$>S%Vv&q)w*0-xHhJikLzOOy1D$Q= z^Xak*x@1io+Q95qg()H#_M+TrO;p7)^jqpMJA_uR+ET~Ssb-?f@N%)zHb>niTbh9q z-1RlkR;zWyx8hkY+E8D$wqD=LGx+zMAoR&MAl_#dst#G}_3xJEPz3wc#ss_8*Ra+% z04r(5mnPaknQ-XR)ePV0#jV|H4j%u}o#tW&o20cG`|wGs82OMagOANO;}@lSL)?Lh z+4Ks}A8~R`eZAIpil_G}M`Ohg9>U#9Ia_Nk8c`z*Z+W6oqm4lz8LK>CJp8{~ddM-| zBa8K_rw4@OEDT|zF4$fVX*$QcKCbn3;jQAl-5+<894CJ!^$JEn;z5uI`E47BIc9jY zqQak_KUBN|6}7c;r3?iThiSf+n?OrN=5e{ZNmFyg1WE&wGg}sf^PFh!G*K^bGBfJ9 zW5X(k483Rkt9Xb?qj-58_pnD4!ia$mu(n;MY=u+;>?t+BQEXJp_lcN=16sz z49u=!oX-lCNH1HApCfM)M7E=u8r1f2Sg`&uqYv+{HpNyR10PQ?Fqu3xp}V~Bk~G(} z^1A3%d43cQ;=JuFgCoG^bcv1dDcfP2#`&>g$zg1xh0J!+fn${_!|V zsE0Ow#AD-jgpS1=|cCL{IHONQPiwE=Aytt9M z#I~nc-o>aLToSIg-_9)Cx+;&*mD;{d3=tzRA!aA8Dx?Xl8Yb+_mrQ{f>jszP2(bXE zUnq`{f5*;Jp-0Y=4>Jws&DSfG=PD_k{b^0yL9|iri{&+LKsd|oBc}@A%SvqB#dr^?6xoR_{+*-u+sY*AQ0u5 zX64=oTFxJ-*w6dsI=s;T>Tyu$k?5^Xf2ZK2Wg+>ilxLLIT+JxbyX&v#95AXV7KMtE zTg8xxxp^EXY&`^)cQY%^IDOPIg_g=b&vd2;XPoo1%5Ur>>@(`eGRh_8pQ@b2lbbv1 z!7YiOBZIX2UVH?ZIO05h1RXwQNEDFhbKXI-yhaw!$%EZy)!5h?f)pZ2&p4J;;xXgm zQki9A;r_|%f~Ti`hD*trJL|CNB(RE<%c7^48ah;>#1@GZUr&~l>-eH5|0%hN$Ilj6 zwO1q^v(;+)QJ1D7VPtR2oEXr$x>|&WoghHU2wM#&2w{YvAXeN5qU2qFU#lJqEMXjo zx!|eHY8Z5g+eX7$ZAGn?Q9t3dZV_g{a$~b7agmuOW-R6{3g65TO#JGz+!L1D1T&7KbQMvL1ueb09maIcqg;vKcZ(vB6 z&*l;&=Av-vEBrzcwoHp_yyoVfAW({l9F?vV3+PvQ3*HJAYiI&9%;Q8YCHAz^3NJy# z;3iaJeJmr45r;jC8(A-lZYQEeT~HTGDax>k+)J9b7_4@dNTgbGDVgwA#9D~#jHWk8 z&xi9D8xy)02td&a-lsfOT#dld)%3|cIr0Tj1+LYlyE5ZyS5*=K{p*TCl>aC}PI2-T z5k7}KeD0`#q{@tCalOgl3vO;};d`a-tCo`}Uyvj=@(|pP(T^?>g_LJbCTJ7=6qDck zu!2%1>MrOiGS1ep7uJ$&Ix-=j?JEE9Q3DMT{QCxwgHOn&Q| zCwdC!d!pNx{aOOA5erHk^zD_i7tNlm-!={HXx=!g%=_T=z|weq;7ofqjCS5Xj_8r& zmFZRe2Joi3u6T|>qx4h=uoeDAcuqRjn59YXnI&7Bm1;P48mWDt!rkb|aBl|LR1~<- zE5Jok@E-UCLhb6ZsH~(O&W9Tx!K>;|G&YTIRuTgs)#iQ!7K1#Q7gRN=XjQtMl?v2- z`^4}6m`7&timB61_{qHGec>^|T1D`n`ty$8)Xfvsi0whV?cN-G0RHW>OvQ+8VH=O%=QP+`@~eCU22;1@a24h|>)yWs56H^~8Rr1LVju3? z3GML(`f7~NJ)TSp$!bpvZ7$c2@P2p5M zRX!Y9{7iqf7=yg6yBTkyy|bP3jsm%X<{H)~$+->7m=sBRckT>1`nvTvj^~RT^3DL(AOx<- z;{t7xDZ%|*$lJ1N_@c5IXGJHvXd8La#gADX*ke0<(RxkT5@mU}lUh*m_+ilPw6YSB z(gk*@vxAT^!B>8o^vI&@Xexyu^JWmO6`|irR_=qL`7DQrVWaW%kC#)dv;h|tWjlqV zX&mu%XPUG~P^dKO>0T(S_zHOzCmoUwBvDU$*`aXWL+VJTjSr5_7CQl46=sK4W16wR zn+2<~cjG)fCYHzhBisG^V+biysSg&oiF7FJM%Zj^h zt#uu45U&~eWN$HZm?Xl!FL6~4lnYR?lmRNHaa8};=5a0wnW@+mJ~+olL^lI05B zKl{bC*cus?s_kmoz3^_OZx``BN`LYQCS`1BpzQRY%4B{2g}sg2w$NXmq-!ai!^tnp z!_@eOl^EM^J0SEQP(gcA5O2oe@>`7Gw$S5M_VU!=s!ONDmilzxA?Tvl2-mQIBFPG3 z%H(GprZcC>)5-q|{YB*dw;Lc=29-U9+Yy9pkZWdD?AS%wBkc@hxrB(ro#TvJshIX^ zeF3oCS}Mej;Zq$fqH;Ihsw`-D0n!mqnUKU)g29Ofs~NH`S4mXG@Y=vOX2r;yA)9P8 z-CHM1nX+iDujx|i%mwp_K5ddrSGrd$QlYP^AaquiQcESoKA_3Sf5@4lO-Bz2R#^=T zmf}vp%z~@`lAEBGF*ZbLT>rtNAxSxSF|sEyrN{@{ZhE^aXmfr0RyiwXJPXTVJ{43F z)j{fcZ*pVjxkj>p1R+{s_C#h!yD|w0V4t@Z_^?L6v9Pd|BcZu|SRUD?v#li7vZ6L5 zumW)E#(w0Hnd!ts9=49YOYZ1XQXy_IQ7GwLngLyW$S)kmp12(WT?l3+IcLm$1ck}; z)R8CT{{k^rKII1jYyI$siY$2+IhuVmKOz{3z@iWv`I_BeitmvcpqfrfV0zGMViGAeY{)#Xx!_YE*WIDd6}sJOVi zz5ftG*e``&kEcYvH`D%m#*k5W4eM{5Fp0agbj=wv-`Goo#=~@2V);OaN|nZ0LD`Mz z%tJEM*7B=>kg2t~?7|7|Hww_j(WkCgoqt@$dPuoVU9ihbE{}sTp(dem-QC?A_~AF8 zM?PTKcIutYAYRpf)fAr#RH3q10L{zLZQsp$q3i5s6jPJ}ZVKM8b3s;TsjQ%`xR zu#{@VC#EB!`%L9Y5;?G##K88fqLycm<3mBM!ktO&vi*HR;%-Q+loL_yh(y@qFWx@_ zEfb9%WW7~Z%Mx+7e>V^QU(0`{WwCJ9U4EJJ-PK8b10>DT@Oj6FswHN%y|p&W=1evG zCg*kb_hxmwEgywPt7laHc)%krWId=ApJ|#!$7ynyhr}E%wg}th>lHMi)T^RV97P;j z^iiI`$n%8$G7l$ATT8vnTtnZPWAyg)NKcZu&4?`q;$Z>Yn~W zntY&uhO_Xn0N<(M-8f*$Km@tMe0}^Ndg3_UWX6Bw>hv_nb>qi?~;u z^%8p?h}YN&(r5`@m#w~D5*(3}qUbWfLiH~9^C*f}KC&*#V9c2QMw?%O*PWov^-9I7|cVKYXOIRBIaP{adg zNuK|e36`hT+oC@rU;G5(H(*}B5Zr4_D=nH_As6Tf8_vw#ObMu=*LDR$OXN07U%`rn!`j?7)i9S zYb2|CF|BK*Enl^c(Ly`Bz+t2{PLppccsFEy!l&?Y!EL!rH#V$FWYj%ko|3v&%hgYY zgl)RUQM|r(rg!X8g4279%|N+w3p$4Q(hhVNh}uQ*P~Wm^{4)0mb)bTFRR86JDnk3=hEh58?5*6l z_@7*lY}ZlJoObi5o7tUl-RkQWoe5HRoh^a30*c_$q72=6Mf;I36W5zBd4w7M=SnHA3uRX* z3YTxfcD$P=q;i+&9 zk#!Cl&ar4N%Z!L`(#{2sVAnEUi)}&lbNh?9_-wa9L~faz?BaY=X-IgA@#BFfXFbx) z&pnxm!b%dmr%0j%P=Gq>xMa998e_|x`^tBtTtFfmxo@+h>)(}NN$ZD{xA2H#L`8QW zp3;hppat93I&7(UW6K^WR2e(#&GeMpM*1~rvF5PZ_|cQR5aYr+T|_{}fJz zUb=KCHktr$sN~>ED=Nu*cvE}0PXjN=p`%m&YsE|o%rfy&kw&8^R>*BJ5b9G^oQ1#A zgd5tTs$lCAb-6Rg1SY4}D5OxmWPS`a47;5AqVi&5xqp5Gcg9Jbnkz!;PssZ^`(Re+ zG$QjCTBNI&?<;h^IT`~+a>+ZCT1x|WMI%U?*s!gbx_WER88!TDzh%5h<9OLV?#11Z z$N+qdv6s_Wx~dLHg%XX_KIC8O=bDDGw2dZ_TpESrEX4k}J)N$SrrWwl>d>NOVN0fG zakG7R!YR*I%VGl+y64|7u4?6E6|Fu03UpfaG=<(+E<(4*f05FEK4Q*lc`!xsbhxs@ z=r0d@QU0xQgx-h<%DeYoyO!5q@4fVH&@bofP$1PGJjn35>l7#Y_2P&e8)`L@fzz(9 z#hISkExd{q;*(|hrS&2U2IJg~RKDIE4}o@%Zg3Onx^@*@k|X2K3t z6#xMIPvCxJ4_d_k7!Q7w4=_*=0LXui*MI;>L`;H;hK_-VjERM}0tyWSg!6j|1^+E< z0P!>bl)?ksCZJSeXOQIe(H+n&u?E0t2?m&6D>fSR7V8rGknW2ZMWpaE%i+FS6g{pK z`fCQtKt*W{e! zo;v_+SrNiF4W$56;9!o67tjCnF)R`|J;18T#Ub%kX-nq?k=w4GK>0@U>~w}|Za0H@Y9hN*OjsJv*x zE@II|B53P>aZq=w7923vj@s#GkHp)>B~5GU6VM&u!38w(kX}>2;GY#xaCQOz6vr)< z{w>fw@lX4TSR(-!jxsC;XO&*ax|;;Z*{}T+nC;4HmoHXw1FpNfetc@eCwtC#aWDpAKnNs% zi|2tZxVlb0gs&L_ZB7=<{&%zx76iiJvP|6-Kz|?0uAY?ux?1KMichTF2vPtP#w<|| z49+RNauA{~NLE6~gH%-RD2G`tr0=6ih=T^06B|#+#l9>p1Sw-6YL$;G61zHSjkjq5JkpXw-uXX!(nU;CaoSKa8Hd zGoX-fyX+Pba9+Z9JLWF4cCh#ByBd4oYy)mGR9xT-U2?q0bgGx>-m#+e_ zz+(>SHcJbV97Fs3S&QgdqTm0(#5c$_V_=~6MtY~G1?|S^vz^edhC=$7(QXhN(Wy=- z{{GD%L{mr3i{A$nAFj~vu_&(JBPW_eg|1XtHKex<#=qXZPx*Je>X#|d_1n70h$b!m ztAiof4RWco8g4=Wuk!3)4iLDNO_!1>K!Hc25({FU2Li=?0PVHq7&h!k%RmJgFdqF4 zU$zK|potgCzYqWqf(=`MpR|gMYBJ-zNLfUUIo`|(f^E*&2~sOac)iP4*0TQAeox8| z(iknJ<=xe8-v@koN5xwpD^VfWt6{3>q+UIPSaPC&DOH)>*Y-OM*2llxn5kPyZeeMc z>ii!Dnyp1|uzUb~?7Sod%R)X*!6#dd9WbvxuQR@;P&bfofreRO3NEsGW^?5WMd_7O z`$Xs}p7DoDj9q|3T{#`0)|Od=D>&8*`SC%OQ@R_>5;2IsPB=Z9vog~MYygonlmL1? znc)@C@AV*}=|zKo0kAK;Xa*D=VR@y%T{(Ij8RbGm zt`~ecF-=5K=YF5%5b<+DbipMxsNxukFqH!dq0EVbxIZT)BDBH7Bhrb=zL}L!;~Si3 zxJ*fMSvDw+`3``(plt{+{tI9tQqyJ3%Dp=X4E%-tgY-DTj0Vd4U_6gDHVMKO(>AGq zL6egJba0?42Rb^W`P)wrn#K63CJTG5EOy|lax;}6hV5=7X$2C!t;(*RSW+uVuxPa{ zDd_YQG%v(TDF0R|g04X1#fAV+4g;lkpfw|~Y7lweDN8t{fWEL#d^Z1kCRVztBo75j zJHsxz$_ZHRwL_@lUe`r-Q!GKbMQ0!^FvgZ+P zCpK+X)^923VwQ6};})S|cw96u%%(C%5lB~hcqbu@87LLUeA{u|mib{(N{&|Ev1SFtQ;cMH*>J%@;pda0u;cWsdArt z@U2Ne^E;URlmSMECS6*pkknC5c{X1<-Z(&-&_AM;L=c(^UKMm8>$C_f;F;5%vvIVWa@t7JxayeIS3Uh5e}G6ctU2NNb{)E8EQk~02dxP2p(m3 z{M5gkC^qKX0OnF?m+15LmlOl_pj}KbfpTEomX!p=r6BfdAi*q5l|}{vWCZ1Og29WApr9s21XXxE7n4n7eN7zbY5`-MRpK`W2m?9C$j4@1Pa_eJ44Dg|!cf+f7H&EgJ&hBf@ z)DN1DP!Z!aD&dr2Rr^(!<<40~=rlxPklc!-W)=bc^6r4UZ;Cf9*Cr`mbNBM`r6^Md zO1?zQs_vxYUS`Gd9kN%uwHrPYrHT0rUA`97*LMRFAG0pDp?dFObj9AMw8aEyi)v>QgI@VjwqZgtUjbe_{7? zityVsE!M|i2;;8eq#hTJfCXPv>k97}AARkCm{eLm_Nb=C7cG_W%41_cb!3j-9)MzA z(H9@N(0k;+HD}<+&K@p)!alB2&j#t|M0mnhWnM(;cwak|v)m`Ob> zPDgccss*8NTmNvEc>lebM%LXBzhKpOLgA&3WRb7P7 zMueVkV`(-ZaXD_jP9d+}q!w88JQ@DH<3aqqXR|(Z-ydM94z>}_Z))h1NVa6={x+5Z zAevHG`EBaYA=-)l?u)jcH8uT-noY35=^ldbh@_e8e=b~~a#fWn>e(eaIE_3MN>0zr zr+{!RHiMDVp@3YGtuk{|<+Pu+J{+PnuDia(F2j*1Y;oz>a>ZYJFo_@Bf!*Wx{Gz+yBzYO~%Fbj(@DinezYXd)*mldt4#H{jwA;S`sv05d zSg|j!Gw~>ir^->2PWN6Qb@eF+UCGLZZ=EwP9h~zy!Q}_Xu=O~Uk-@uR5L7(P;UmqL z5%Ow#y!;G%!bs};2U*nOasv~{nA&)qe+V)P_vA^msVDM@eThMylx%bsG88}8vbZ<= zn2j%^&Ox;p&^VOW#gx=(Cam*^mO312uhsj>z%Ylh7*~-lcGr-9+XCBt^Ev)u{*PsH;bdp*~K-sZ=?5w{60k@spBZ=l2B z-;5j6eumE!|2J<2YwV>PzL#B^QRy)JU1QW$yfoT@(=Aq86TY{F*7`{oyF*oI7Ev4^ z?cj%D6-Jvsq0*Ekhng+LSt1P&6bZoufKt@~PE8-K>Vj{9uXk0KM=qn* zyzr~xa;g_7;nnx{tl<*VJqj+jwZ8eeh+ScMFACk2s~fD9l>~2s8F*JM;k-S1@^IHN zAg^0Yq2(B_&~Kw|i*kjqTV$o{Ys^ zx-~~n7SJBxoLDa~ZD4QWWhy3NktBfxfTl|ZvnLpNgr z%|HnqJ>)MdqU>pR-vEW-Q2EyCW!}j<&Ie2tNo!DoV+oc%p?xoK{Z=GbM1t|D>MawcByTf8<0-3V53u{EoLSoNI_@cZ9@ zF;VI{ACayOV@^CR9CWh%RUIMeJ<)q3QlK80P0c7$jT(s$5kRWnSUPCKPQE)NO5OIO zrR}!9bdW_RZ)HQx(lWYNwS!m=ByD0yFpGEInFX^hWC<3wp_#Ljf1*Fz-{!hnFDweB zCNo^TM8Yqe4PM2L$IPSLM24xV?b+D+3o2XkmC69yR?rAIo&dnoV00WVh+oOnHqf+@vuv|Vz;Pfu@4Tl=@B(a@nE1)S9E^d1!Uc$lgq(Gs-1ZA-4oyE7IEOWQ-CvOw9; zYx5q9WP21O|NEKd0euN5uX3c=yH#4^A^ ztt0&nmAdvi*9!Ce6axYMmS}y-Pb+Hh=|K9XbKn7wT^fXmE;}@P>nef8*oc%7(#!Ah zSg;vYRoC4iC4K9Y;6}yTIcQLR4JvDb)#^y==o>J_{=aZ25D<|6Y2pV$A_DxET*dff z%@HUh+`IiDQS*2IuM3?2=PY8^kNogD->8LiD_(&?F%BMbZ)$BxBj02e&UDd}GA1CY z7n(DMYDq*jr2j{fE5X>RYEX}al!zmkL#spU`hm%+_TeBsO#yQUzz4&>~H|K z#j8Hvq0vul_U*B|WRoN1Gj$gpiE!S;~NkT zIZUzCs`m{*mdv4S-X)MBk_j{bC&$F5M5Cg#NUM~3lwo=q7A0m-UKE>Twr+jS%8Dpw z-Gv_L8REZg1iCdo%O)MUp0O_P*99)m*Ix zGaW*HFYkb1J0tigvMQGT4&EIqroIl0R1vE(A0_0T+(eR-Iu)y>K$i3h(s=lG&zarf zeg;MJ${Jg)yj`Rra?28quIwDCz3+q;Vcz7CD|I@P0v?^+k1v5nUi9nbw(4_WqHvbr zNNdMo@%`NK)7RwQ{g%|oywi2Mc91Q%>@Ud$WlFbqVR=qQ{fXKauEB8o#Y8mFTt!DW z*)ZgS0Pv3h7Cj0X#Y1LoXcF3;DVXj*Lgy_O0 zi6scg2WXvspE+4&JOO)lYF%jJSsvZ>89YhWV3Y?D#ZWoLiOM9TBrGOPNxOnr zB@G2#rr8pkEx`}zR{OM72|Q7Nsje!QPJbc~L<7&^|5Ri7;VL^M{ z+^ty480OzI1*w5VFrlXtr53Dxz76o_2wiXXRjgOnP(<8*#ct$MH>fYK$4!%3Eu=7Oq`ReVT5D{33_X$u$SY(DVid8k z_xKT#nGKN&gT#-A-h<%e5u?TeKl^mkyz)^tW}{d^GdU)4ppeplnc_JFY15Uf+`p|Z z21XzhO8de)J)co{88i2rQF`UVaHOcZ%Oa1M9^G#M1eGJG6Zry@olUuu(x@FY=XSfC zznmp5I_+-`DV+$nD9y~9b8#EYLeQyrwt*~>W~49xeNq|pBa_1*T88fk%7FMgi2XO< z?jWH|Xe1FT#4PuoHx#ocaX~Et3coulIjncyMvCJz)NqJC2S?mEV4RXa!k^PL0TfeB zrhpgm;*+i60ga703ris!58aUsiSx%wqeZQ@A+)&VUr=mX9zabJVy|S*LfN?3K$1do zAT3KSZcc|%>3Ja8dF$3kCjf#1{b{2%n}$P zQj{Vf8AikARniUxnn3J%HP@QlZ!vz zSxip4v;75_dZ&1rGK}#qT1lHwvtG+4X$CIUF(Kzvl)cJ9n~@kv+OGoYSs0ghAQT>_ zrqhPYbdCauuipAgRkNQ8lD9SKR!VU$C7DZgz!y6*tHT{uEZQ?(Y94-4V<^RRnzBBF z$yzN*3Ga-FfdvGzqP{}oK6S)@YQiR|7KV|S zJjCc9+|cS>tSx~7#+{jv0US{V&(uilRc|1sQ@Kh2DVYeQAta_olF#lSX*OE{2S-Rt zuWUhU{0S`4HEwFMHlqrM2`G+`5-C%-il>8z&XO7{<6qbPV z#0=UDUH6(n!omGu;{fHR%oyeNB&?5&)SviiKZ|}OFG^jTXIFJoLl6QiCu*kj3d(|W ztkpN}An1UMQayk!{jNmE2`Hpe7=|)3Pt625x1J@@@W&9qL4G6Ps~{@}mKp%2BZC5u z*d2K-!7FI&KqcBSN;D{j50}4~TJ29&C%I$rY{QW3arNtIi3K(aebQ>OAiM-!F2jgV zppxfM2y@)sBR@Q@Ei%|vYxws@L6FJPGUT#73reNKfHOb=7^1F)?*|P6nAX zvg%hlpf5Ai)xN}dgi9-%C~5>LLY-@WN=bq2U_w=6Hlx%OC9lPE;*~GUMExVmkfQ}j zhhTyA+_yo+Wi;MWxEdg?v+&cx%6^i|L;#?2LM(ID4e;JQV8d7w#fBWn22dTJ#L0JG zvru1=z5%1DOW;{8GRxz{^bWab7jh>FoVl8%#V2&+jXBg1DVjoB6v~UK)r8@*9 zDm_)uYyp;|9K#?qkdTmhV(9lk!qd{YIN)zUjW|fKf(p0|_Kyn@8rN(L7bwA{m*9x1 z_jmb#CRc`n+_C&oA;@N_eFJUVOFs$#>M`6+G`3GnHi$^rR*PxKqLmB4Qr(piR(cC%rktA`!-`0KB`+686t7AuqXyng z+AItNlc2!MDQpmbXtKxYM-VLM4tG1~YTjq(l?Xk@b~7YYVU_2{H)kc7WdPOVRo|`7 zDw@u0BX!=N3NEQpMu&Lt?h%C|4p#m`phqpUBjmEDjUT7GbRb8t{pT4RQuj z@5D$PKM#HjA_i&zQ8uJZVJ8A?W*n9PpoE$X;I<5eYQQ)ZCbT6)-j#^AheU*w=hRxs z(z^F!1eau6aRf6=v2Yv&la0CVpXYRb#KwaQjsr4-EaOdRUPbN++EcN#F%>4MXZ-s+ znXp4Q4YzqUoIW`{H~=XjA!>kUxk>5IqZ_uyuFoZZ|`$pwj)1n;kT_mXL zQ8TN1i~^D=JSxy$?-YtQ2*OgC+7l+h>+SH!@FAgW;lQ4~1*^exl{ON0x=aKe%|Jhs zl}|CPp#?KF7+$J8!ln9QoI_e;)z$cJcH@oFKxHn%Yuw zIX&qm$gkgQ!HFuvtt9<(8jf}Y6?*;w_VMuJ*@NU231!e>p+O<_OZ#**{@{8-tWxbh z#WO#?uFmM%pijY&6Ue-ErNPccwti95HO_}`+Z(~R{5kGHIl`;3{Ws?=XiN*&x@z-a z_Dlm8y^n|AyB=$s@4lANbE)BRB}BAm3-I5J69!+! zH@;u)?Gt`w;-@~Y$3ZX zho+k1z<9Ug9`I|287CQbaqp3xS5s>*HWK}eWj(s&#nqmEEkTYL1`!kOaD4+~Kq>2Du$?C-9SE>i z?MS6wZs0!tne-9QEUixuhRSj-fd2q+09loXzu+(P$)laAiGBKb_D<72{{VbxHcFOj zDcYWm*|zaDEwU#%ci$j!41qRf!1`M!qQ_$$qIit0OuS}U8oDXN&&+Xwmx&P`9-Zm7 z_Z`@!w7Q&khK#Y+_v^-OeP6pvv;o(yo8eTHgvq95qRm4rM!G}U4R!2`Q0&EJ@a*AK zUT<+V2b^Y2ysOgaYlQ;Vpo zsiSKub539Bsx%}&IqD?jNw&b;6*nyci{zmXp|aIBP}l%;r<;xrx6{aaZ!{f7;XjOn zJn+;MCr%>UvbS70t=_S-O(mo>ftYJf-8gtzmhf<(&Jz!@`!B#z@Q)iXj0H5Vz7qw3 zITA9@>ptsX^sPO z-Vkgc#_C9T{JD)!D3j@5l+~3ip{hq7Np!(VBX=SJ#`&K8c=lH8Eeco_bt0m&tMqaw zASZ#g%j|U}1qZA*>rZ=g?v^LVm71k(hQb(Wrh+m`TTbFg%TYVJYYEqlua6_QhS}`` zFRin`(N#-It=bE!@1|QclC;xtkQdDq&(bNCh00;pC0RcY%{{VmqNl~IfB<>bc6oW+q59&%v)DV$X zD9{b${>cjo6B_bZgkk8HRikVpZC&GcZJlZ69a}H7TXC~OllBZemRt)~hoSAop#Fl* zywhoEq=V3~^9BC^@dFx^@BrJKAwGU*r%Ax$Mn|skJ+;=F2(z5)D7E8w`LS zZ+UMdy6a0Gn#^1QmU0ZFjAz_Fi*15M3Eod|Hkkg96XzmbT((^R%qh)=9}hhca~hEA zKPyQ2TfNGNYBFyE!Ifo>%M6}zftOCjPt2YfS#$USD*9cRn0KL74j*9Sv@!JS01pnj zX=|H&uk@~3{os?!gHWu|^DvZ0GW+Z?k?E*$Pj+2)P4|PpF|jjdqhaFX-p)RUr{@&_ zmzk|o;4N|aJ-u9@%mlXWOCODJ4w>*iuMgJyzKS^KZcx8Q3*m4sz`SvBurY;-2>1wc zapA=Q#1*b0bEzZwc}YT+Xq{|^m$Da3uzAN_GO76WeC(!NN_O}@!x?@1A>q_&pO~{P z?ZjZOTznJ9QBhKHrQ1GrmPr`!v6p@mde}I4`gz$w!_%n_o=UcRZDvf zsY=Odd^5Aa(b8-AM#yPyB&hQSUv-n>L1 z16se9Ge3=&W|rjg{ZFne;tFjhjeZf|ZgIL8h901Km#sbJ&R;f+n6(? z=c$hQ#dr5x`)GA|rB66hrEBE0y@y*?Vu~7AJPV84ZmqlU4D8_X;b9T9m7#6rj>Jdg zG8yotkhLMu_uu32u7~194ViZZEugj1Gn_fDni|E5|+}~b@33blkGZO$L1daVEV!vM&_}0e-@SSr+t8+1AwR4Db~^H zEDJ!7q+*$bg{!W+O^_|iCnYz{o68$GM7YL4MebwHzyXlRqZH~Exw6;b+K5z4G(#_A z^Ks65t#vMh!t+P8!)+WIfe{b~W?mh+S`O^X;AHK~7Q+}!v4LC`o;$tv{Iua|-N9{B zXZe&%9M#x?o(>M->{%JuN}*LOl|sbQDX0NhcT=~T#XRQ%hzed+p{87IvqDN2LAVrW zyHmGbHZ?jLc~FfZ=hB7fLXUV!BG_np8OP!EVc}jBKiLMA;b$P-WiD-dAS~o7h(_P7 z84R@Dq24>JVMAz$#PzI>{ogU!X4^@IiFErG2f~9}G}2syh=|h#))8K(^t13wV7LmB zydPWurpVu^v|KV-TGoOCK`f*nc;uPxM^$%ZoB z#f9R>caMHX$%iXG{{X9A%W`chTBkhWZTg6R`k<$e3r5SJ<595lR+@5icVGI#<;**# zL5|BJTMHtDaWct!484~(hwwI^LtfP+I}+SR7r2K#eO^753VFrR%`_d`hJ&`ykm`hm zC|ZI`%MOTi=+bjgB<`(F!gZpFAXHb&VdXIDT~1{-Q_9%GK-fm!-3K1KI})cCCtT35GAI~1pjg~TN7(`SQpr=jIZA|Tj( z$D+|t&WHQcQD__E7mgv?=J0TwAMQEO9{XOK#O&qK4$G8;XZZA`kT}~w)`z`NvbYXsDuO6lg$C+5l%=SxWT{FdsYKM0btLa6_OMD# zQb;304G8ZRf}2G`EhIRtnu4M-T{!Iu6;p!A2TZG5N6u98)TTj7*0$YJ@(2!uASrFP qLK3B^J4I*C&{)StDGNfP9a3DT-qNa7K|l(PRZ3(K(zyI0#fHjN& literal 18323 zcmeFYXHZj7*e)6bjDmoTB7}efA|N0rJpmCR(nNX(kzPXY9h43Mg7l{JUPB8Vl+Zhb z7K-%V37y0DoqO*2=FFWr_x`*y=iQSvYtLSLt#?-T-s^dv_gOa+H}impGE&k~00II6 zz^B^-aB~e1kx-HpS5Z(BW;8Y8w6HZcWpw7@dc*k2$-&&(lu=p+#whbe62|Dv&&&3j z(ay=h%G}Y75y~jUM{+X_kO17d?TdimKgv7*VMK)gQ4$jo5fa@czI*pSf57|q?gH-v z@7}#fdhb5Tf7os90V&CY|2X-NPX7C?cZdlIiAjKWf&ba$|5I_(0(f+fK!@Onkl+R2 z&LaZCM+7&20AK(C;MN&J0>J-m_wL^X5)j=XCLz4NF8vTdKzM7nP6GkNKr+C8v?nAY zz6-qf=>Fs9Pk3I4D;hkt^CO{T;{Bi$O~w57+iz+X|MJ$M1wmzXLwg73@5qb_)ZT4Z zpxX&0AigdBr`;j=?_J$qefQ{gn*MVbqFZKN388Nc4ZI|7RZl|Mn2vA;8}Q zfLp1uXYcMGSsJ00uGGh6BP7h1@}7=QJ3aUylXUKX|53;A`xB!0#{fd4DxI`(z%%Iu zIy0{!^(Hp#++F_*lX#tz%#`_rs;f(_itf3^Ru1WXR`ZaQO2qxw+>GG&Ox>wfv;qFw z`!o)l2t%vv@=;}&BJorSS}=d^sV|k-s2kZri$8^^o9a5UB23`{-n?@ap`QEqu$Ms? zg}w?fQA=(l1!vpgQC&6mDBmUeG;~T8q{@_i%+MVS5;i{xVIWj6EW&qAe^&+KIn{Q@65l zV6pzsAP0Z*zlkw;M(DwLV7s_J6T@rop98@Mnu}cqOj~M0%oHrDIcYTaf_-1S00al$ zdBOPqu1eTqciG_c*B9}xkUr~GVFS2QSCn5iO4@Iuzp7$6_K$1Kqd#Wpj&)K# z$If``AF>G=bT+?DaFL)X<5^<8_y$jN~f&{jEjoPJ10s{ zx2OD$TsRC<2W*OodMVw9fUQEjsrQxeCV@Sr^Tww=0WIGJ`g6--5>Na7^ zg@+fVq4&_NkcV;UC%(tkeNa6fvt~63_oK6I%cV<^1gzcmOlLOW%F}Z!^`R$4o?fKb zNCUMhk8tl3#eHEqT*e}Gt|q)rncpni23G=JoMEu_jAVZ-kHW%`jw`=kH@ox}r=djJ zr?@Ju!0mzNqFzQ#I2!>Ok`(u8xPw0Q0nhhYz19_SS})O&R_6V-5s%n1y_ciKzqiAj zvFDqjX`si`EZ0c|`J;R82lB6?WOfNk9#}m>dX|@n&cqsNSq|5Kb(c>Xv7mZVoeZMZ zV)N2sE70YW{pte=;){XyEqR6zAqYT}Wi`ZA5da)N4>?<@yFR>gZQY1h@tP-~Lb>7e zr4Wt8Ck?La|N=;v)=dA*2R>AlXWD3Eb*XMzZlP*)SPCZT%!_aoTQ}{dC%N z$u7ms>8yR}PI>sEX18ZyG+?USchkDgtZB}kw8Ijy{6ypEta8k}!~AEMXRKpJ_3v^O zyVvFC+em(XQH>kG7k7!}wuyS7Msxn^tsnwJ~s(Z)nj^m3)`~)r0aJn z@Og+9GDtjJ7x|7g?`SK7@NU{wsj4a}jq>MM@^!G+iu8T_fs%dn+77hm$~wRfqd@h^ZM0WGI?$wm68g{Svlx!upHrS!*qBhfuyb3>Y+RxtcJ!822E zFwN*Sny+9T-?Gne72*P@M1nP0>vUO{>+*l>M8YbJ9`FYe+RoYS4Xr=idohZGXNl+=0n3viYk==UHs<+#*V-4Zf+N*zVW;cM9&uZodsEKk9Bee$>T`-%BkOr8nq};@}aId5mfbNPa z@X6kj-AX7Lj9CV|eeu0z%+b>{_%i)ZUKp74NS*)pm4+X%GH9J=`luY6r8 z?~-T9mX)2cxYts%keKDel(5TqadvKBGBjF}@-wF~Mi}#Vswb~NN8HUzNqcdOL1uh9 zm2AbIw78_2JxME1o0IS3V<6_WHK3}`c2At>A*`0;D8C>#GY45x;w%A@@D56{UHdh&DMS>S1Os8fHj`H%cQ`^*i#U^x+fOmq4h1(<~Z)aSXAt%+pO?W zLkVQd{b?1cvFSx{m*66YQyq)@`0@6j7uVE^dbTzXmd9b#GGLs8Zr53u4JT{ix5HVG zhp3hMx@!nCb0EDt#+T5n0aqC+)fw99ElXNJ%FBa$U5jvE$8V- zzB}WxJnxFn*wlMbRTaBj1(CG*d;jI|?r36qMY2QzZOCYtl9ToFll3(#LC{Hf@zIW0 zP}=%MTw@ zooeC3~Wl0R-d3F~qkq?a;p9IOf3p>{J};HF9h2JyO^rY*GY z&(3Oj7n&M#7)yQ#n{+&SQ8h(-?4~1Sq3qLxg$^+`NnqCMhz{J?#dde|tw+!{=Iw8b zF{{P7MJ43cVxiMotY|%B@X$yL1+zm7H-x8|H(%C8R~sGcbhoLGiZ4~?9jV;KzT3A6$Nrjm>>)|fRl%IUHrJtiBU)}%oq z7OV@6_IgLI)RIQ+u`)^*%y%69g;=c zP^ES0CQMlAH|@`qv?{EP=&mW?C9k+Zk=D=@is-H$VZ$9$6&Ho7L?=r%3$vVtw=8Hy z;FqCb!=qmb)jU%a&EVCQ{69^Jc!)HZEl~#Cq8lUr#>92CY z`8=M^a|4dj9kLijzs;yCZrgMOl9zg5@>#8eVmh8OwoP7@d(1G1n-~j* z*_ANuDdl)X-!VlW`umEu$W%nn{omDEc77Dl(J@rD$d!E3zq5RX`^Gx-O_=~!;uaT* zU{`hq^SA|cyv^c^?qdN9&74QR=6#_TisqOOc@rmN$(2ta!X-CA+`Ge71Yp&#i>N5_ z_fI#Fhr>W-Z%z(!2luU!H0x)?KUNhxV4jAk{LX4*h2*|ipm*WLtA%!|^Hm0)wPeU; zJdr1+dCZ{RFK^5p4r-SDjThmgQk!~gHI_*5FZ8|;s7W6$;Ssr3*skmzV8d!rL;BJ% z$wpb?SSuD^;4X4P9r~d|wOWW_YPFr&`zl=-14M{}a;c2lnC{C6N&W@^eZz_^OAzbu z+`2z)<0|hbwXhz(^Ay0#lI}PG#v1WQ5A*&Xn29Ho56?i1?5rmRe5-8TZz5RNb8};h z{cALN71g6ZV=wKr*3vMx@2}%Oc|>A|7Azvf+RGaS+qPY)SU+`JI;G(#`9Kd&ENNze z8(v-K$g0^bJ{kDJ@!8menbD+raH!$lzZ0r=cwXU1^OQM!cx2w{!3Ad(on9M_<|@*a zBYxbnZd?V+f`@#^O#LXBlbRGWAr+&(0nk8bMP$F9LTA!kml*mi$YwX`4;6~|H}A4I z-F}*FqZ&tAU=Gi1M%KHSE;-xoGgj6j+oau2ZU7h8%cL&SNg{8?IfDIgd)lsn*Cr$&mb&_6F(aD}HvYyiIHq|9dVVKrGkF}eRy0_$T zu>7q?{!tt6)cfuCIaj-S3+*Ke;GYrad;ve4QJz8RWANuKanEK3BhXUt^Mor&q7Bm`@8d0hdUnPsy6_~l9EAAo9abZ`0%b{rK&cWWCvDJeIr4$ z$Jc?hlMn#}!$wxJDPSDS*ym%7pSu}R^77_QeGgDKfRL}2Q)?lM(fFMkKrt8FtLYxE zYBP%@qkOYt7U~4}tBb>+8mHcP_ZvWsyy%QGke@VlYVtMj#|2`|uiQWOcPK{EFDac& zTstzi(jhm1=u~@zWe$at!|!tamo^@nbPe~6SZC~x)ncOOUJDkZa0eo&rz+v|XqgZ~ zEzvpq%(!(YOL)?9t~!*ptB5Z?k3GXSu!KfypuOjcmuF}Ga3Gz)a!tC+%|SEl+(oLs zb(Uq!k>gKBO1{`KIDyY(tj37HsmqZ+9%V_}XHP1g0O4>?P=#GS0h3Elt{BBQjX zb-B@X_G6s_@Qg&98hzgB4dDE5_4Ze>AjgQv&WPpoZ3y{XSlYt8|5N*>;pK1s>)D!= zJLZ;l?2%^~*#nTOUoP!T-a&{vQ3>Q`_GH}ECirii5t6@DRO@DG@7etE>Etk#dHkF2 z9e#6eWW~jOJl!&P>#1U#xNxiyHNDEfTL(p@i^k8| z%Jk4`q!6g1Wg_#nF^_>|ouM~Xl;{T8uIZ!P_5D(HvbRH?CAlw$>$f#&R8O{6Wm$4E zXJx26cTb9eXJ9YPl3MRNYkpF4#8z4<&9AOPg9@Ygp0EH^=i(#ZQNkX&D^OifC!Q!W zX*5+0&z9AEV06`_wfO>VtV(SmJtzBdgsHeq03ZKYEbar)#-{o2vdM<9Q+2-8x_T&b zeewiK*Pe8e`D?cA-;_0X$;G>~9CJ>|u@sa>Aurw7b&ib`?}`))r@2cRhyq}*Cghk? z-1%-Nhi($wa8e_z*Ktb0xwC%;rY^`DA#=nA!K|_?(HK*YBvMv$$6-GVk>9oJ{(135 zCiJz7U4HM=-f?%5d$QkO9B=LXOt^{^odJvL`S1-)omv++z7n?JLAsqfYSUlc03ta{ zHdP|j*(5RGle5OT+Vf}Yg!Mu$q-9X1zMUFwy(Z5WfoY4ZKFp)yH2$BxuZCiIi>rzX zf*}mHg+WIa@}~KdAR`EW12dI`P$SIq-@NUVQEGN@LRN z0!E5L*t)&jXKMiC9z6~I@qcmMKu{XA;fksjmlJ5195bDxn~^AP$hKRp`5SQy7FP(- zY(8t9G>)(rDk!lgv)@+8;R^97JWt7fm$~ig@&Hrk zmlW5GVGdyXyYBNf(;ThjB2BMF=krx(YH2&VcY-e|lw2L?6yyYxJ`I$EoZr^^2sn(} zrQnW5a?=$-bG9`RG+p+MZ^x3ISpV%L#FigdQ6-(yVfMd$<=Pgb2Db!}L;RoCP3_>y zaQSO`V!vr#m&W~^6t4g(BE1r0jh6v(B8k6_jDg7N)pk;FwywP)A!1fnwIbX+sz z74X#M3$D?W81BrT9p1&^iP!y3#KXcNq?u8p41xR(OZETgvpiex^q-0fic-@T z1Lu~vG8VhIG%QyR@f<70@31P@vP5!geZd=4bd`0loWuy~7S-Lurw&!2IondFEWy%B zIuOVm?WMgPK}LJ3jtwZnNXYx|vBa~#uT|qkVy#&fDe0T$R;Ye3z}~ z-$V2F_Sz{=uJnPaa@#%J6w=%>L*QYVz{#0I;r(mbuk zN|5t~9d79qG)cpRBv>ZY8d><|uv5sKHSL+sTNY2G)Ug?@JKZ#Rr8ec%Y>93SHP4>r zuQsa`BZ&QC__IGLi&w^3YCWN~%R!uC5d+%4#Xjh>mrWK;hc&ogZsXi2FA2<>6zvx! z4hL3oUCb}c89uGlqu)?u(ww?_bdqz;dvH9nT^$h1SRw~9dWVEJH0uQMwTC-mT7ss; za?i%IW*iSFPSw0p$0!np-d4LvLmk2u-ylJlER>rOjoCrBf z+qudOYoH9-zXDG$JpGZ*UCX3|*gh(xl=W+;uIHp!5+zv5$eginNYpB_b%n{2# z@w?T@8?Lv9EP3#aai(v8WgV)Gj;jQ9y;)40Bw5CjGL%*b1rEv_mTF^K?1=8nl5ecl zhiy0u)Xa=$syPmChu})43Y}}w1Fl&}HYTIuAD7T=`mNPkU?dQ=m&3SqZSA8vz?9z1 zu&l-rl%69xH>9Z$@-B||aDVRN1y)0zZyc^VNrtB$5|Qs%*6N9+mz*6ikL}B=)lOu6 z+tNSB#q5k6S_(`DPPCD=>7ts|PRb>+w8hqSlQgZLRyo&kYx3!lt}9qQ8kN!;@0(aJ z(D^5#qxGK5&TfA}=lQnirmxTv1;8sB_ISYYT3p4G&E0gv{ZfgI@$dGaJYB-cmp|C@ zcK=}-A>dkLG~o1==vvmSo~*gS*B37+{}+OpyStzu2?10YSAjj112swO(T+#vvrZD6 zXM`t*Z9W?@w2Vbg@`*Xq^kVY{Y|fzw6}n=N^sBT8#^^qm$(dId(M~gCUJ7^Pjc|N? ztn8r@fB9i~%97#JpU%WW>Q@}Ry=kcUEbFzMl42C`6UwUQ4d{pot>5)QFvrzMW9d|tcj~s9M(?cd=| z8&}q{!}2wH-gW*V7Ch^^sLK6l-7Z(j!tIMD-2F+7eqBL+yK^7uuwKP+e&XHA9i=NJ zi)YCelpps#8{_#^AEkl*vHA)mR3=g7C#MG1yP3){iIqH1T$W4;Om~f z23)bmaYe?Ndl7;}OHlXqLC-wTFE@^Z&I6DS4NBIlv!r}jN&FeFg2;C*v0bsRWvT4L z4b$FrKW#iI<8;}pRDo)0O}t&)kKf$@Dov+%w}$Y0yak_QNj?{=2bb9F)u$q2GG{j8 zNgioCne?n5iFJDrH(1aV|m&-=VU*7OHdwYK86ggX*H>t-aJYrYLm z>*m7HPK)v3dqXsZ+~eJqCJ&(v`#fGE6RRQ!s2}s;f?bH=2}r9I7P*MBQ~IK)%ugb0 zra{}8`E~vsMVE)vY{6?|Y!bWsv#eYLPx)F~18%T3Zh#5lUD#*iq#d>m>km}wAL`NO zM7lyt5Q*>)U0>`pWI-_(QDRTJ`C?cL3xtyk$QwtL8Kgo-m@7VAXc8TgrnBwZ5n{M+ zA-?z)?6n<)8~$@eeS2r~nk?D!rDA3^`E(B)Po=#%Ck{b?}(11#} zA8G3qNF1hC*x; zrDBZsvFtxLU3}2LU6y5O+nV*r3`Vsk3;9r7u^WqIJv!sppt$&hF9^Aqs@ytEWYiMH zka4w^uE*FFZ{~23m;#%Xqx~02c}0CityM?|V28{S){euZx#2lvHv0WG5mOHBp_|po zb9UMzIy&RHb)5nAfkO=IL8s7l4pMaU-Ef57csRT3CXdx{#q)WT@aL20^8@t#v8qgB z0rc^jJ2+y&9lcHBe3TRu#O1>;y=lTRMiNg{V}3V5fEuM~kYW6Qvf9E%#=`9VV%nD4p)Dfb-QL4mn79$hCFTcec_;*he~wY{?xStlwJMgK}TVjFzyEAVuV z{7Vm5K|xItIypXTz8Zuf`1?HyhW)-%90s4ll*vo*D4SiQ4e*+s^gjn@3=xKBJXa-R zzcf|2;f0cd($BQw1CIOHvof{9k-DyWqDE5FE}umBGLlFN`%<&p{XG)0n^L!2p0sBm zgtg`1TL62StiKf_=KgoDca5w~!K8wWH-G>foEhS3#(?a3_gJ0={3P4?(wV=S!~`Mw zJs?$ag#TVUjUeGtvl@oZC)Vx1rY9jVh`nf-s`a7Exk5$in@DxOfOr9|oI1%5t(KX` zOUHU3?Y#qtCu{fA%tJyI;8lkHWk6o@>3Gl$piR{xD}|}1;Jjj)M#|~U4PXc39CH$Q z?lbV^MScvgFM}IB!U40!&>gGohPxZEQwY2P{3N?KD$H@*h;(<-^W%(*(-S&cj%X1U z@HQOiiKRg6Y+neueqaG3) zc0GYEn?(`QbeiUsDQLS^>KHnvlgX*%z}h){_w?oz=B`#fL+evi59S~O2OyqQRzXK& z+yO1Y4dC8A8-(gB3k79PliAN}r3rtr?n7%*DO#qf)rsr*ISw04h|e7^?qu9?ru(xV zDdQ-6;Na>N`+3Ob6&Jo^@EEB7x!{{X{ziULB6G1v3Uer{ENDnns#=^b2?OMr*;5E0 z@mZ3kci|A`^>QIcg(Z<69emTgCK*nFW%G_5XDVAR4}aH=jmyIn zaF>EBaM0%?3L6H}NL#(RjNC7MJr>rBHua9y6yKi?{dB7&D6zY*%i^N)bj(>Ow|~a`GLY9;qnytYco|BDj;( z>mv_f0O9~ES<2BOx)OSFhN8qmUPulOiTm25uQ@}`#AZqDp;9K>Io~m-@@>>+cb@w^6VtPl{}zU47~^o% z9_~9?`0Y_#l3`m^&liXnfs&BCDvvZD-0NkdxJyp`xOU&UOs)LeR0Yyy?rmVbKh68gQPlQfi%Uf&U4PTpdi_nmB+InS z0S>s}wX#Q&iTE?e80e^Sgvk=+p1(`32M08+>)Lv-9?i?#=(s)lC}R zk%n<@6P@fvAYl$qPgGh<{|M9B(&27T9f{JS|8a*#sq_c=5^YaS^URioM^Ag6|5pW+ zMcc0}Alu0%tLY@_@Rs?- z|9DR>Ydvra=GIj`q-y_`PDr!TQ75K{vaS8pFkk5t25R8 zq#Y5br^4A(Kdfu`^6I5kLEmYcmD|h>YVS2qi9^U1it^14ygwg&Smw5=f=ck#6t**b zEE~Q|NJ@+GGOA7P^>`B4?yZFtC2Ce^GF|o*G!7&Sbb}j zqa@wHzfyy)#>SH!(qeBq(uU5F8Ou(_^i3hhPGjXG?KG!LsSDvY8=e5vtV1IwBgQg4 za^Yp^8+6%8D8!vxiNF5pEV5_IN`&tH8`)fwjUY5}jGEYt*2%!97`0G&Ve_ z+_9>#E`RFEWKN$I)dF!_92hg6t2hod5h~FwsV^=enzGjksjhwL5ps;Xe+)DiT_}#S z_g+wITCUo(9n+H5*Njuw8dQR@iu+ zCQ#SHi|pSG0CYy&5ocC!6f$PB8&L|L5B}jz5mkPvEqen1F4VPYC>M+>>4mwRz!Vn> zrA21>>s}~Wj7NS=8bOFW8{j!nl^ED#2exyHG@}J5IK}qZA6u(uE>^m}%*rgEe5rsKolb^Qisfn~B0wJjnxh4BQv?;PoBBX^bBo&CvdnEm{KR;($c+ySei< zKsg5=GBIds6!IsJkOK^n_=$!G*onj-Q@E?!OjEdelz7|zzRk#m` zyvk6o>p??onk8R)t#Xai&M4K?CuzVbifX4b&k!bY60_bq4}p^=X!TO8d{sqKq?d<9 z`SL_Bg_WvsjQ@F?h17+6)lbP;?L&vf#Yk^z=?iw}qeJtcNQLD?pUCKp=IkQDeR+sG zQ+btF*gDrQ3%-&*#F|sccsG0PO1(;-xlwrN%cxIWu8?>;4ES25d{n;NaeW%$BVROr z$qy0Z2sKf7@@6yqC`R>eln1jvb)e{543VI76q+`siR@*YZGn!d#Y3%v{LOXEcLF^Q z;@JoRlu(H&!!|=JDCRPEu5&@M;rpw)r5fRscqX*8y80KLvyBZCmVPlMg&MyzThv(- zx?pDMU!9>yvs=H3ecZ~#tlHY1{P6m+UeX*YW8CUbCp)_hYHt);Zg<)1klZKYcQ`Y0 zfLB=#(t1@iNqhmW#A0-yHWtl%oXsC9IbxVSq|s^e0}0k zx;7VNV_JB9tq_=^JdG6#=skJ8O}IEu$#}vtGUq0;*hidcbsAvF#(AI>MNSo_D|vGaIx9U_+QPv?2*wEABNK)<-wyd z4multrD?(LNawiS8VP&H&dJ%Ft$6rTE$PCfIo*^wv4-5Ws-t+Jf}@y}q{M*jUB zS7xLuxLeYfqFh5Y(+*ASlN(ynRaE;|`Q;7Z+quWKNc#u6^idmFJB1G;l6W02oLl=BIa0Q5&x3P zbKFL~tLf7O~_=r?=E5Epi?Oc9LE&nY)-R*}Lv{C+i5DsYBeB zwNnH$BV7hlh0yl@B;Rb0I9%zVfN(Wj;o5A6J;_@bXm8la1;n**8hoW*ceOZ57E)qv zH-C$)v{y5405YjsL$9wl(S4KRr=BINvWmnxz2`Z7P@%lleM&xZbIxP>E`ncaLUyca zcPie$hyL>U0XZBHeY(HRe^$033gV3CjX3B-?r`T?uX77|?HV7U2mEx{S&bV@tE4p_ z{2YjBM6A4Q9IZsz4(kk4qzM841W!^a1dPn$vEa!=%*}8Or6U?5ZIM5pcX9&FSI)iQVdTQ8jC-K)CyH>hvx=4FMdQE@1Irl{#zuWQ1@v^fB_?pBWYcixWnv!7AX?vr@2e zs_%A5i&dAIl%boRb?UfPFT^kHwO*9NU6jg04H%cD+s=Ukwyo}4ivowQ?f~NfrFki5 z?f~f>r`pl#Yn}EW&TWP_`7pO6&4vixjlVuK*9f>JSJy)2(oZa@k^Jj!n4cj7)*a}SF24OS7PC@k0Z9cA0hxT+r3*0i#Y$HnGD%MTeB;b2z5fwAg znJ@0Qgk%g7D-tFUlGAz)dk>HF+f?RKb0=4y5C9IlFtJTxl~%w#Ki&u$*ij|@1W!QlM^Qq5UJ7HL$kO?uum?nK|Q*unv*Ht zc*_7>h4--kG#|Ckt9x8SJ1dpqcswpLmdSt%Owi`pNba%Vu6|^THgfU!@tXP4DRzw0 zh`09feZ*VL#SC$>5knOdidN)T&A%ylv_8`@9Zgao_1(H1Tg&$j3BWW}j}@FSug8N5 ztjfO_LZ+UTyto&u-3M-54v|RdC1l++S+mqhS0P`WcgFT@f;YkVB&d~(_&w*#l2Hs5 zawE3`V7Y4KdpAUPVyL~Aby`7R{8#>2OmFtY)Znf2BxmF8-TeOi)0;t&W@?}@x4u|Y{O%>mkNBYrB$$tp1mWr zP{cnEFGjK8VeG@z!4a$~;A8<(j6TP_7lk8S~N6Bku<^Drok+i;DVu%T8D2}@ zIv%!Dvn)XRlL(KVCXC=ir+D3Nqlv7K>~dS>qqCc4dFT3)p7W)4^4P76L6kmJ@-(PN z=?3t?3K9GevcKoA<5jEvd{qAzzg;~9MxQyoQ!nvjz$h?7_yX#wOv{m*Kc$5~XN@{J ziS8l?iEI_L>)!xO4hsUKp!npwp(9HrHC>yEdSyj{tO|6A%N0DOj`l&WOSeIs8eArp z)8sda-ax^1L}23Yb(eASOmw(K$rogYH>5@&NUkY>wm5bJN{)BW#Sk|kM^~_hNlw~t zwK{c1W-;UB9xozN2-gl|gXwPo5--ug$6D=WIv<+B!(0CXu~xtR`ijufdfjduJ>u8I z0<2LZ7=jkJJs@FsMx)sn_*Wd=(z$%G)^WaL6$TI}>Nc+%8zC)f~_2Cc^1!o|tQ9E!YT&(8p z8lhpM9zr1(B_QiGnTQZ;H*j>2pR}!MUZv|=LIS&P0KXR$QW<8rVmAY-g=ZLE2Fm!) zT5wj8Kgj=aMM@EKhWK#CJxp&f+Sq6kz3Q&+dWY^GalC1MsG#-{#E(> zR{QF;bFs+zb2?W5ReC(aRgZ$G>>+^B+fU8J*GX_xCRE0~msNdRD7(y4KQOI87GZr@GIgg^ z7V^p(y`WdJsGa;#ZF=iW?VC;S_orXC(*NDMYK`_Oi zv2Aw`!+P^__`g7}vgsUn_3cL@v_V}*cSEOe)A)`wYf-|bvdqqoM+lQYV4j>((t>>X zc+OcXAb%P+bXFtLfDX+Zt03 zT`Z?p{2jA2oHC4s<#PC%nM6uz{cD1+1Zf7a%!k{Ke|obinskEgx|oGgA4HhRj;d9(ylbpo?~W>`pQPj2a!WRwQEmTVW_b(0ByCRW>>qf??yllxr>@x<4=>9f zdn6C|Exoz{G(>ESnI5XHU58=OBA?tsk3Dl)a!QxJ1z#o}a9x-7%*h+0yNYNtqh}91 zD|rgzY8c57YX(riK@@U=4iooBtPD&s5Ich5`HbhM5^ty5Z8^~e-6p|C4-0IVo1Pu> zyz?QN*>Dd^eD#lPtGTi+M-b|DzAI~l`XZ{y8e87}K#pY?k_G^{F~=S$4&Oc{&c{LP z@|kJ^^+C<--6f+DskK!`{l>%v0w^jB6ODMB9tGD9v{)3dWdD_VYX&YrhfTelwn@~@ zp{+Nee1FjpCD*djWb$yrjX{*zLHC?H$K(%_nI#Q?z}UQcuhIHnL3jUSABTedrgEnR zciW+&m-90!xv#eHOEGKciY)g$@4l6(!Xxe%;}M8|stHY|Uw<{LV0U-LY$kV5YG ze6;YnUr1}1@^ARVK|lAcNcGDww1xK_#hyy=Or41bfM{V%_~ zOW%56Q+OWFohP!w5wT2F$N3EEAG#>}R>UNyINK=qn3qO*n9zgv3SuahOBv!yLJW5%e z3F{uXXkGNaFn-Z&#(}0WNPMa(8o$Hgpl0^Z)~~>^o_Mf zK&Hat4CQ##^s8@K#(D7gO0Zl`vIuJmNKbG+%q zRq06Gx0(lv8GhR#zC!ylCW?`>dE6KX!ZF=<26Qtm3I;p{j2zOiiti?H#X4dEV8^Wr~qwp#cCoTQI`BDlL zG0RO}OW{Ki1KWqTqj#4xk{Z8tF_A5ARn0tOnh-7d3hldkueomqUVb0(%4C%L< zs){4-kEKHTm@goi5o5($uXJrE>jVw2;uz}dCQe0r5ZXr!%Z9Zph3j#KEOInWZf3R8 zFrmN_cG++n&&L$b&|e1_uSjB#j+r(3R=AZIbh9;dAai}w_2wkdD4FS6yI0&7Cs#G3E0R&+%p zXYZEM^k4tf5|4XCA0e;yWrtzpSzWqL@9YwKQ(GIGouJgj5u)4o$V4)1VH^`bYJBShKYd4u9fsiM{sZ0yHkK| z@uaxCv3?4r3@AVwQdL_}I?Eke)K#Zu6j!e;xcsACjSR-jocaQzXFS;-b-#iiiUq&N zpK3s2A!24RdT-%A8$L;fZo~^)mFs&!4CP<=Fx4-4`Fl7{uUEU6Ld4z=9gsX6nKsW4 zc}@K12B6%CB`(nE(E5>;&e;STZz32fRNnJqe-e5FU_~v>x*fFPX)>)|s>Oepod?E6 zFL7miGf3;qP8Gc01uwbXE-HL!v!EM*7WG|-2N-$-;JX!id}>hr66AF&-)EasRd{1E zyxDfb`#r#Q*`uO)zKxS6M?6?3*`xKqG9ahqZ4dja0$sWwu-BCM?Ghy}Txy+ceHKu$ zm^K=Y2=xDrKrBqItNoqcTIrr8j2wmBf^@BFS-I=`4)jM0ZUYfhHgALjOZY{FXti`G zr)r$>it`OQxnS+$Yl_593$YwE0mm8CdY;O9P^EW-Bg+jygT8gqy-zXYO8unVCpWWs z>FL|WM9a*8ER1c=*VnuqZU8hJXqNuq>-j6~I7u-jSBDM!fr!O#p{^SRX3yF! zNlzD+z_PCMr%REdf*QN$0(lyj1H)6vON+E#eeu+a!V2?fld|#(f&&-JFi)R}#w*dc zvM`m-_1_^Y?`3sX?r0w$RGN9Wf6n~;=wwEDAzqa_<>iGbkRaRjiTWsq{EXy?ZIV1+ zUy&(kbAFKMn7X*lB_&k#5mYIoI?x}7;kHo_}!m_$Vj{7KrmQ#&EDtY=BjZ3bZ zJprPr@?4qcky)w!$jTm%8Ox`)(M$q=dpv`T>Uz)PFfthyDTN; z`_$2gKo^m*=;r$#!8UEf?-y~JexNjJK%}?pd5IvzZy;c3YI>w9D}Q7ul5+ew8Rwyx zx;>&}e>C~p{?p;fN69FQwik?IFGt=_%C+4EMq~15g%%HWEte4k$SI50n#37N=+J_< z^ztl5WJop*uc?*VhYKgqT|x-EKD&WQa;Ql5Axnk0&sV9N2my4rSG!fZBbJ^IrK10$ zR>$s>8hgowxd%Ihujgv|hRF9{=)8?v)al!sG_wt-j8?51Tj@v90zGZG2 z(Toi4ztUvrd~G@t*JrvYYW$^7zVz5KRF1^!b1yPY;lK3B{W0`wJyMekT!#*)0V!fw zjx`x=CUL7fxoOKs?Sf;_mvxh3nlV*A!m2v2P7s|>*CN+uhIk4sy848l+n;Ja#yH>FA7yUk|UHBnFbFUk6 zo>)zrlWAbHGFTiAv28p%mR{!DSu(1Jw>YuNI*NcsuJlF~ep#2c$+ zrYywuqen1lY~_^^#iT(rk_J}QdY6w*<0`y_bV3Fp83>%zXz{!`aJUoL%K9G4H?ud#VwUqz5X zRxeG<*fX7}t=i9D5v!sqoFNmh@gi|HGj&GR>;2A97lfdNfw3kw2V+{U2!mHlv8|gg z*jRlT`7qA&eT{Yyzs^grm8$3(&6B^06Xwsw=pCyyU*<6U+{1VH zoX1ZHSV{v?n%#-}L+dFUj%XX1FH2L+~D#xD7=aqLa zna0g)E%W%Rit(gPv$CwRETa9rePw(S9qkI0EnVH35h6jv?K0T-vsE|>5=`ZpODR~PwzCT3LPJFao c`vrEuUHk>MKSmGxAiDX86#7GgS^xhg0G4d3+W-In diff --git a/templates.go b/templates.go index ccd90c7..7d38b51 100644 --- a/templates.go +++ b/templates.go @@ -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" diff --git a/templates/404.html b/templates/404.html index 3b3d64e..bfdf439 100644 --- a/templates/404.html +++ b/templates/404.html @@ -1,7 +1,9 @@ {% extends "base.html" %} +{% block title %}{{sitename}} - 404 Not Found{% endblock %} + {% block content %}

-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/API.html b/templates/API.html index 11d9ab1..d58e398 100644 --- a/templates/API.html +++ b/templates/API.html @@ -1,59 +1,64 @@ {% extends "base.html" %} +{% block title %}{{sitename}} - API{% endblock %} + {% block head %} {% endblock %} {% block content %}
-
+

API

Client

-

To simplify uploading and deleting files, you can use linx-client, which uses this API.

+

To simplify uploading and deleting files, you can use linx-client, which uses this API.

{% if auth != "none" %}

Keys

-

This instance uses API Keys, therefore you will need to provide a key for uploading and deleting files.
To do so, add the Linx-Api-Key header with your key.

+

This instance uses API Keys, therefore you will need to provide a key for uploading and deleting + files.
To do so, add the Linx-Api-Key header with your key.

{% endif %}

Uploading a file

-

To upload a file, make a PUT request to {{ siteurl }}upload/ and you will get the url of your upload back.

+

To upload a file, make a PUT request to {{ siteurl }}upload/ and you will get the url of + your upload back.

Optional headers with the request

-{% if not forcerandom %} -

Randomize the filename
- Linx-Randomize: yes

-{% endif %} + {% if not forcerandom %} +

Randomize the filename
+ Linx-Randomize: yes

+ {% endif %} -

Specify a custom deletion key
- Linx-Delete-Key: mysecret

+

Specify a custom deletion key
+ Linx-Delete-Key: mysecret

-

Protect file with password
- Linx-Access-Key: mysecret

+

Protect file with password
+ Linx-Access-Key: mysecret

-

Specify an expiration time (in seconds)
- Linx-Expiry: 60

+

Specify an expiration time (in seconds)
+ Linx-Expiry: 60

-

Get a json response
- Accept: application/json

+

Get a json response
+ Accept: application/json

The json response will then contain:

-

“url”: the publicly available upload url
- “direct_url”: the url to access the file directly
- “filename”: the (optionally generated) filename
- “delete_key”: the (optionally generated) deletion key,
- “access_key”: the (optionally supplied) access key,
- “expiry”: the unix timestamp at which the file will expire (0 if never)
- “size”: the size in bytes of the file
- “mimetype”: the guessed mimetype of the file
- “sha256sum”: the sha256sum of the file,

+

“url”: the publicly available upload url
+ “direct_url”: the url to access the file directly
+ “filename”: the (optionally generated) filename
+ “delete_key”: the (optionally generated) deletion key,
+ “access_key”: the (optionally supplied) access key,
+ “expiry”: the unix timestamp at which the file will expire (0 if never)
+ “size”: the size in bytes of the file
+ “mimetype”: the guessed mimetype of the file
+ “sha256sum”: the sha256sum of the file,

Examples

@@ -92,7 +97,8 @@

Overwriting a file

-

To overwrite a file you uploaded, simply provide the Linx-Delete-Key header with the original file's deletion key.

+

To overwrite a file you uploaded, simply provide the Linx-Delete-Key header with the + original file's deletion key.

Example

@@ -108,7 +114,8 @@

Deleting a file

-

To delete a file you uploaded, make a DELETE request to {{ siteurl }}yourfile.ext with the delete key set as the Linx-Delete-Key header.

+

To delete a file you uploaded, make a DELETE request to {{ siteurl }}yourfile.ext with the + delete key set as the Linx-Delete-Key header.

Example

@@ -124,16 +131,17 @@ DELETED

Information about a file

-

To retrieve information about a file, make a GET request the public url with Accept: application/json headers and you will receive a json response containing:

+

To retrieve information about a file, make a GET request the public url with + Accept: application/json headers and you will receive a json response containing:

-

“url”: the publicly available upload url
- “direct_url”: the url to access the file directly
- “filename”: the (optionally generated) filename
- “expiry”: the unix timestamp at which the file will expire (0 if never)
- “size”: the size in bytes of the file
- “mimetype”: the guessed mimetype of the file
- “sha256sum”: the sha256sum of the file,

+

“url”: the publicly available upload url
+ “direct_url”: the url to access the file directly
+ “filename”: the (optionally generated) filename
+ “expiry”: the unix timestamp at which the file will expire (0 if never)
+ “size”: the size in bytes of the file
+ “mimetype”: the guessed mimetype of the file
+ “sha256sum”: the sha256sum of the file,

Example

@@ -141,6 +149,6 @@ DELETED
$ curl -H "Accept: application/json" {{ siteurl }}myphoto.jpg
 {"expiry":"0","filename":"myphoto.jpg","mimetype":"image/jpeg","sha256sum":"...","size":"..."}
-
+
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/access.html b/templates/access.html index c61cf51..908ed06 100644 --- a/templates/access.html +++ b/templates/access.html @@ -1,5 +1,7 @@ {% extends "base.html" %} +{% block title %}{{sitename}} - Password protected file{% endblock %} + {% block content %}
@@ -9,4 +11,4 @@

-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index cda6746..86c5a29 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,5 +1,6 @@ + {% block title %}{{ sitename }}{% endblock %} @@ -16,12 +17,15 @@ {% block content %}{% endblock %} @@ -33,4 +37,5 @@
- + + \ No newline at end of file diff --git a/templates/custom_page.html b/templates/custom_page.html new file mode 100644 index 0000000..ac00341 --- /dev/null +++ b/templates/custom_page.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block title %}{{sitename}} - {{ pagename }}{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+
+
+

{{ pagename }}

+ + {{ contents|safe }} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/display/base.html b/templates/display/base.html index 935979f..d60f4d8 100644 --- a/templates/display/base.html +++ b/templates/display/base.html @@ -1,36 +1,36 @@ {% extends "../base.html" %} -{% block title %}{{ filename }}{% endblock %} +{% block title %}{{sitename}} - {{ filename }}{% endblock %} {% block bodymore %}{% endblock %} {% block content %} -
-
- {{ filename }} -
- -
- {% if expiry %} - file expires in {{ expiry }} | - {% endif %} - {% block infomore %}{% endblock %} - {{ size }} | - torrent | - get -
- - {% block infoleft %}{% endblock %} +
+
+ {{ filename }}
-
- -
- {% block main %}{% endblock %} -
- +
+ {% if expiry %} + file expires in {{ expiry }} | + {% endif %} + {% block infomore %}{% endblock %} + {{ size }} | + torrent | + get
- -{% endblock %} + {% block infoleft %}{% endblock %} +
+ +
+ +
+ {% block main %}{% endblock %} +
+ +
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/paste.html b/templates/paste.html index eead748..84335d1 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -1,33 +1,40 @@ {% extends "base.html" %} +{% block title %}{{sitename}} - Paste{% endblock %} + {% block content %} -
-
-
-
- {% if not forcerandom %}{% endif %}. -
-
- - - - - - -
+ +
+
+
+ {% if not forcerandom %}{% endif %}.
+
+ + + -
- + +
- + +
+ +
+
+ -{% endblock %} +{% endblock %} \ No newline at end of file From c61db30f9fa3f9bb6affe2703ba715e33b3a29c5 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 13:44:28 -0700 Subject: [PATCH 095/233] Tests: Fix image torrent test --- torrent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torrent_test.go b/torrent_test.go index 1d227fd..f14c613 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -68,7 +68,7 @@ func TestCreateTorrentWithImage(t *testing.T) { bencode.DecodeBytes(encoded, &decoded) - if decoded.Info.Pieces != "r\x01\x80j\x99\x84\n\xd3dZ;1NX\xec;\x9d$+f" { + if decoded.Info.Pieces != "\xd6\xff\xbf'^)\x85?\xb4.\xb0\xc1|\xa3\x83\xeeX\xf9\xfd\xd7" { t.Fatal("Torrent pieces did not match expected pieces for image") } } From a0c508c422b407bdf64167e93a4e0688a793d343 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 13:47:57 -0700 Subject: [PATCH 096/233] Travis: Update to 1.14 --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7baaebc..46278b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,7 @@ language: go go: - - "1.10" - - "1.11" + - "1.14" before_script: - go vet ./... From 7543c82473b43760a3dca7a7dee81679462e96cc Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 14:18:12 -0700 Subject: [PATCH 097/233] Remote upload: Add direct_url param for redirect --- upload.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/upload.go b/upload.go index ea3578f..8526260 100644 --- a/upload.go +++ b/upload.go @@ -184,6 +184,7 @@ func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) { upReq := UploadRequest{} grabUrl, _ := url.Parse(r.FormValue("url")) + directURL := r.FormValue("direct_url") == "yes" resp, err := http.Get(grabUrl.String()) if err != nil { @@ -215,7 +216,11 @@ func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) { return } - http.Redirect(w, r, Config.sitePath+upload.Filename, 303) + if directURL { + http.Redirect(w, r, Config.sitePath+Config.selifPath+upload.Filename, 303) + } else { + http.Redirect(w, r, Config.sitePath+upload.Filename, 303) + } } } From 53e7aa456833f337be01bc7d2601fdbce236f7e6 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 14:27:25 -0700 Subject: [PATCH 098/233] Dockerfile: Update to 1.14 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c1a2f2e..b5a73bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:alpine3.8 AS build +FROM golang:1.14-alpine3.11 AS build COPY . /go/src/github.com/andreimarcu/linx-server WORKDIR /go/src/github.com/andreimarcu/linx-server @@ -8,7 +8,7 @@ RUN set -ex \ && go get -v . \ && apk del .build-deps -FROM alpine:3.8 +FROM alpine:3.11 COPY --from=build /go/bin/linx-server /usr/local/bin/linx-server From 7212cd655572eafaf965ae7666c954b0a7454e6a Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 16:35:50 -0700 Subject: [PATCH 099/233] Highlight.js: Update and add languages --- display.go | 2 +- static/css/highlight/tomorrow.css | 77 +++++--------- static/js/highlight/highlight.pack.js | 6 +- util.go | 143 +++++++++++--------------- 4 files changed, 85 insertions(+), 143 deletions(-) diff --git a/display.go b/display.go index e15b1b6..6ac87d6 100644 --- a/display.go +++ b/display.go @@ -100,7 +100,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request, fileNam bytes, err := ioutil.ReadAll(reader) if err == nil { extra["extension"] = extension - extra["lang_hl"], extra["lang_ace"] = extensionToHlAndAceLangs(extension) + extra["lang_hl"] = extensionToHlLang(extension) extra["contents"] = string(bytes) tpl = Templates["display/bin.html"] } diff --git a/static/css/highlight/tomorrow.css b/static/css/highlight/tomorrow.css index 9bc0645..026a62f 100644 --- a/static/css/highlight/tomorrow.css +++ b/static/css/highlight/tomorrow.css @@ -2,102 +2,71 @@ /* Tomorrow Comment */ .hljs-comment, -.hljs-title { +.hljs-quote { color: #8e908c; } /* Tomorrow Red */ .hljs-variable, -.hljs-attribute, +.hljs-template-variable, .hljs-tag, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class, .hljs-regexp, -.ruby .hljs-constant, -.xml .hljs-tag .hljs-title, -.xml .hljs-pi, -.xml .hljs-doctype, -.html .hljs-doctype, -.css .hljs-id, -.css .hljs-class, -.css .hljs-pseudo { +.hljs-deletion { color: #c82829; } /* Tomorrow Orange */ .hljs-number, -.hljs-preprocessor, -.hljs-pragma, .hljs-built_in, +.hljs-builtin-name, .hljs-literal, +.hljs-type, .hljs-params, -.hljs-constant { +.hljs-meta, +.hljs-link { color: #f5871f; } /* Tomorrow Yellow */ -.ruby .hljs-class .hljs-title, -.css .hljs-rules .hljs-attribute { +.hljs-attribute { color: #eab700; } /* Tomorrow Green */ .hljs-string, -.hljs-value, -.hljs-inheritance, -.hljs-header, -.ruby .hljs-symbol, -.xml .hljs-cdata { +.hljs-symbol, +.hljs-bullet, +.hljs-addition { color: #718c00; } -/* Tomorrow Aqua */ -.css .hljs-hexcolor { - color: #3e999f; -} - /* Tomorrow Blue */ -.hljs-function, -.python .hljs-decorator, -.python .hljs-title, -.ruby .hljs-function .hljs-title, -.ruby .hljs-title .hljs-keyword, -.perl .hljs-sub, -.javascript .hljs-title, -.coffeescript .hljs-title { +.hljs-title, +.hljs-section { color: #4271ae; } /* Tomorrow Purple */ .hljs-keyword, -.javascript .hljs-function { +.hljs-selector-tag { color: #8959a8; } .hljs { display: block; + overflow-x: auto; background: white; color: #4d4d4c; padding: 0.5em; } -.hljs-line-numbers { - text-align: right; - border-right: 1px solid #ccc; - margin-right: 5px; - color: #999; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; +.hljs-emphasis { + font-style: italic; } -.coffeescript .javascript, -.javascript .xml, -.tex .hljs-formula, -.xml .javascript, -.xml .vbscript, -.xml .css, -.xml .hljs-cdata { - opacity: 0.5; -} \ No newline at end of file +.hljs-strong { + font-weight: bold; +} diff --git a/static/js/highlight/highlight.pack.js b/static/js/highlight/highlight.pack.js index b9d3c80..ea2203c 100644 --- a/static/js/highlight/highlight.pack.js +++ b/static/js/highlight/highlight.pack.js @@ -1,4 +1,2 @@ -// @license-start magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause -!function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return w(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(w(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){f+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,f="",l=[];e.length||r.length;){var g=i();if(f+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){l.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);l.reverse().forEach(o)}else"start"==g[0].event?l.push(g[0].node):l.pop(),c(g.splice(0,1)[0])}return f+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var f=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=f.length?t(f.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(y);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(y)}return e+n(y.substr(t))}function d(){var e="string"==typeof L.sL;if(e&&!x[L.sL])return n(y);var t=e?f(L.sL,y,!0,M[L.sL]):l(y,L.sL.length?L.sL:void 0);return L.r>0&&(B+=t.r),e&&(M[L.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,y=""):e.eB?(k+=n(t)+r,y=""):(k+=r,y=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(y+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(y+=t),k+=b();do L.cN&&(k+=""),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),y="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return y+=t,t.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,L=i||N,M={},k="";for(R=L;R!=N;R=R.parent)R.cN&&(k=h(R.cN,"",!0)+k);var y="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:B,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function l(e,t){t=t||E.languages||Object.keys(x);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(w(n)){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?R[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;E.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?f(n,r,!0):l(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){R[e]=n})}function N(){return Object.keys(x)}function w(e){return e=e.toLowerCase(),x[e]||x[R[e]]}var E={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},x={},R={};return e.highlight=f,e.highlightAuto=l,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("prolog",function(c){var r={cN:"atom",b:/[a-z][A-Za-z0-9_]*/,r:0},b={cN:"name",v:[{b:/[A-Z][a-zA-Z0-9_]*/},{b:/_[A-Za-z0-9_]*/}],r:0},a={b:/\(/,e:/\)/,r:0},e={b:/\[/,e:/\]/},n={cN:"comment",b:/%/,e:/$/,c:[c.PWM]},t={cN:"string",b:/`/,e:/`/,c:[c.BE]},g={cN:"string",b:/0\'(\\\'|.)/},N={cN:"string",b:/0\'\\s/},o={b:/:-/},s=[r,b,a,o,e,n,c.CBCM,c.QSM,c.ASM,t,g,N,c.CNM];return a.c=s,e.c=s,{c:s.concat([{b:/\.$/}])}});hljs.registerLanguage("haskell",function(e){var c=[e.C("--","$"),e.C("{-","-}",{c:["self"]})],a={cN:"pragma",b:"{-#",e:"#-}"},i={cN:"preprocessor",b:"^#",e:"$"},n={cN:"type",b:"\\b[A-Z][\\w']*",r:0},t={cN:"container",b:"\\(",e:"\\)",i:'"',c:[a,i,{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TM,{b:"[_a-z][\\w']*"})].concat(c)},l={cN:"container",b:"{",e:"}",c:t.c};return{aliases:["hs"],k:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",c:[{cN:"module",b:"\\bmodule\\b",e:"where",k:"module where",c:[t].concat(c),i:"\\W\\.|;"},{cN:"import",b:"\\bimport\\b",e:"$",k:"import|0 qualified as hiding",c:[t].concat(c),i:"\\W\\.|;"},{cN:"class",b:"^(\\s*)?(class|instance)\\b",e:"where",k:"class family instance where",c:[n,t].concat(c)},{cN:"typedef",b:"\\b(data|(new)?type)\\b",e:"$",k:"data family type newtype deriving",c:[a,n,t,l].concat(c)},{cN:"default",bK:"default",e:"$",c:[n,t].concat(c)},{cN:"infix",bK:"infix infixl infixr",e:"$",c:[e.CNM].concat(c)},{cN:"foreign",b:"\\bforeign\\b",e:"$",k:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",c:[n,e.QSM].concat(c)},{cN:"shebang",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},a,i,e.QSM,e.CNM,n,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),{b:"->|<-"}].concat(c)}});hljs.registerLanguage("puppet",function(e){var s={keyword:"and case default else elsif false if in import enherits node or true undef unless main settings $string ",literal:"alias audit before loglevel noop require subscribe tag owner ensure group mode name|0 changes context force incl lens load_path onlyif provider returns root show_diff type_check en_address ip_address realname command environment hour monute month monthday special target weekday creates cwd ogoutput refresh refreshonly tries try_sleep umask backup checksum content ctime force ignore links mtime purge recurse recurselimit replace selinux_ignore_defaults selrange selrole seltype seluser source souirce_permissions sourceselect validate_cmd validate_replacement allowdupe attribute_membership auth_membership forcelocal gid ia_load_module members system host_aliases ip allowed_trunk_vlans description device_url duplex encapsulation etherchannel native_vlan speed principals allow_root auth_class auth_type authenticate_user k_of_n mechanisms rule session_owner shared options device fstype enable hasrestart directory present absent link atboot blockdevice device dump pass remounts poller_tag use message withpath adminfile allow_virtual allowcdrom category configfiles flavor install_options instance package_settings platform responsefile status uninstall_options vendor unless_system_user unless_uid binary control flags hasstatus manifest pattern restart running start stop allowdupe auths expiry gid groups home iterations key_membership keys managehome membership password password_max_age password_min_age profile_membership profiles project purge_ssh_keys role_membership roles salt shell uid baseurl cost descr enabled enablegroups exclude failovermethod gpgcheck gpgkey http_caching include includepkgs keepalive metadata_expire metalink mirrorlist priority protect proxy proxy_password proxy_username repo_gpgcheck s3_enabled skip_if_unavailable sslcacert sslclientcert sslclientkey sslverify mounted",built_in:"architecture augeasversion blockdevices boardmanufacturer boardproductname boardserialnumber cfkey dhcp_servers domain ec2_ ec2_userdata facterversion filesystems ldom fqdn gid hardwareisa hardwaremodel hostname id|0 interfaces ipaddress ipaddress_ ipaddress6 ipaddress6_ iphostnumber is_virtual kernel kernelmajversion kernelrelease kernelversion kernelrelease kernelversion lsbdistcodename lsbdistdescription lsbdistid lsbdistrelease lsbmajdistrelease lsbminordistrelease lsbrelease macaddress macaddress_ macosx_buildversion macosx_productname macosx_productversion macosx_productverson_major macosx_productversion_minor manufacturer memoryfree memorysize netmask metmask_ network_ operatingsystem operatingsystemmajrelease operatingsystemrelease osfamily partitions path physicalprocessorcount processor processorcount productname ps puppetversion rubysitedir rubyversion selinux selinux_config_mode selinux_config_policy selinux_current_mode selinux_current_mode selinux_enforced selinux_policyversion serialnumber sp_ sshdsakey sshecdsakey sshrsakey swapencrypted swapfree swapsize timezone type uniqueid uptime uptime_days uptime_hours uptime_seconds uuid virtual vlans xendomains zfs_version zonenae zones zpool_version"},r=e.C("#","$"),a="([A-Za-z_]|::)(\\w|::)*",i=e.inherit(e.TM,{b:a}),o={cN:"variable",b:"\\$"+a},t={cN:"string",c:[e.BE,o],v:[{b:/'/,e:/'/},{b:/"/,e:/"/}]};return{aliases:["pp"],c:[r,o,t,{bK:"class",e:"\\{|;",i:/=/,c:[i,r]},{bK:"define",e:/\{/,c:[{cN:"title",b:e.IR,endsParent:!0}]},{b:e.IR+"\\s+\\{",rB:!0,e:/\S/,c:[{cN:"name",b:e.IR},{b:/\{/,e:/\}/,k:s,r:0,c:[t,r,{b:"[a-zA-Z_]+\\s*=>"},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},o]}],r:0}]}});hljs.registerLanguage("delphi",function(e){var r="exports register file shl array record property for mod while set ally label uses raise not stored class safecall var interface or private static exit index inherited to else stdcall override shr asm far resourcestring finalization packed virtual out and protected library do xorwrite goto near function end div overload object unit begin string on inline repeat until destructor write message program with read initialization except default nil if case cdecl in downto threadvar of try pascal const external constructor type public then implementation finally published procedure",t=[e.CLCM,e.C(/\{/,/\}/,{r:0}),e.C(/\(\*/,/\*\)/,{r:10})],i={cN:"string",b:/'/,e:/'/,c:[{b:/''/}]},c={cN:"string",b:/(#\d+)+/},o={b:e.IR+"\\s*=\\s*class\\s*\\(",rB:!0,c:[e.TM]},n={cN:"function",bK:"function constructor destructor procedure",e:/[:;]/,k:"function constructor|10 destructor|10 procedure|10",c:[e.TM,{cN:"params",b:/\(/,e:/\)/,k:r,c:[i,c]}].concat(t)};return{cI:!0,k:r,i:/"|\$[G-Zg-z]|\/\*|<\/|\|/,c:[i,c,e.NM,o,n].concat(t)}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"link_reference",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link_url",e:"$"}}]}]}});hljs.registerLanguage("lua",function(e){var t="\\[=*\\[",a="\\]=*\\]",r={b:t,e:a,c:["self"]},n=[e.C("--(?!"+t+")","$"),e.C("--"+t,a,{c:[r],r:10})];return{l:e.UIR,k:{keyword:"and break do else elseif end false for if in local nil not or repeat return then true until while",built_in:"_G _VERSION assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall coroutine debug io math os package string table"},c:n.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:n}].concat(n)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:a,c:[r],r:5}])}});hljs.registerLanguage("dockerfile",function(n){return{aliases:["docker"],cI:!0,k:{built_ins:"from maintainer cmd expose add copy entrypoint volume user workdir onbuild run env"},c:[n.HCM,{k:{built_in:"run cmd entrypoint volume add copy workdir onbuild"},b:/^ *(onbuild +)?(run|cmd|entrypoint|volume|add|copy|workdir) +/,starts:{e:/[^\\]\n/,sL:"bash"}},{k:{built_in:"from maintainer expose env user onbuild"},b:/^ *(onbuild +)?(from|maintainer|expose|env|user|onbuild) +/,e:/[^\\]\n/,c:[n.ASM,n.QSM,n.NM,n.HCM]}]}});hljs.registerLanguage("brainfuck",function(r){var n={cN:"literal",b:"[\\+\\-]",r:0};return{aliases:["bf"],c:[r.C("[^\\[\\]\\.,\\+\\-<> \r\n]","[\\[\\]\\.,\\+\\-<> \r\n]",{rE:!0,r:0}),{cN:"title",b:"[\\[\\]]",r:0},{cN:"string",b:"[\\.,]",r:0},{b:/\+\+|\-\-/,rB:!0,c:[n]},n]}});hljs.registerLanguage("scala",function(e){var t={cN:"annotation",b:"@[A-Za-z]+"},a={cN:"string",b:'u?r?"""',e:'"""',r:10},r={cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},c={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},i={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,r:0},l={cN:"class",bK:"class object trait type",e:/[:={\[(\n;]/,c:[{cN:"keyword",bK:"extends with",r:10},i]},n={cN:"function",bK:"def val",e:/[:={\[(\n;]/,c:[i]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,a,e.QSM,r,c,n,l,e.CNM,t]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",b={cN:"doctag",b:"@[A-Za-z]+"},a={cN:"value",b:"#<",e:">"},n=[e.C("#","$",{c:[b]}),e.C("^\\=begin","^\\=end",{c:[b],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},i={cN:"params",b:"\\(",e:"\\)",k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[t,{b:c}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),r:0}].concat(n);s.c=d,i.c=d;var o="[>?]>",l="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",N=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:d}},{cN:"prompt",b:"^("+o+"|"+l+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,c:n.concat(N).concat(d)}});hljs.registerLanguage("typescript",function(e){var r={keyword:"in if for while finally var new function|0 do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private get set super static implements enum export import declare type protected",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void"};return{aliases:["ts"],k:r,c:[{cN:"pi",b:/^\s*['"]use strict['"]/,r:0},e.ASM,e.QSM,e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM],r:0},{cN:"function",b:"function",e:/[\{;]/,eE:!0,k:r,c:["self",e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/\[|%/,r:0},{cN:"constructor",bK:"constructor",e:/\{/,eE:!0,r:10},{cN:"module",bK:"module",e:/\{/,eE:!0},{cN:"interface",bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,r:0}]}});hljs.registerLanguage("makefile",function(e){var a={cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]};return{aliases:["mk","mak"],c:[e.HCM,{b:/^\w+\s*\W*=/,rB:!0,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:!0,starts:{e:/$/,r:0,c:[a]}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,r:0,c:[e.QSM,a]}]}});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("rust",function(e){var t="([uif](8|16|32|64|size))?",r=e.inherit(e.CBCM);return r.c.push("self"),{aliases:["rs"],k:{keyword:"alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self Self sizeof static struct super trait true type typeof unsafe unsized use virtual while where yield int i8 i16 i32 i64 uint u8 u32 u64 float f32 f64 str char bool",built_in:"Copy Send Sized Sync Drop Fn FnMut FnOnce drop Box ToOwned Clone PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator Option Some None Result Ok Err SliceConcatExt String ToString Vec assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln!"},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("xml",function(t){var s="[A-Za-z0-9\\._:-]+",c={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php"},e={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},t.C("",{r:10}),{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[e],starts:{e:"",rE:!0,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[e],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars"]}},c,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},e]}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",a={cN:"function",b:c+"\\(",rB:!0,eE:!0,e:"\\("},r={cN:"rule",b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{cN:"value",eW:!0,eE:!0,c:[a,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,r,{cN:"id",b:/\#[A-Za-z0-9_-]+/},{cN:"class",b:/\.[A-Za-z0-9_-]+/},{cN:"attr_selector",b:/\[/,e:/\]/,i:"$"},{cN:"pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[a,e.ASM,e.QSM,e.CSSNM]}]},{cN:"tag",b:c,r:0},{cN:"rules",b:"{",e:"}",i:/\S/,c:[e.CBCM,r]}]}});hljs.registerLanguage("django",function(e){var t={cN:"filter",b:/\|[A-Za-z]+:?/,k:"truncatewords removetags linebreaksbr yesno get_digit timesince random striptags filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort dictsortreversed default_if_none pluralize lower join center default truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize localtime utc timezone",c:[{cN:"argument",b:/"/,e:/"/},{cN:"argument",b:/'/,e:/'/}]};return{aliases:["jinja"],cI:!0,sL:"xml",c:[e.C(/\{%\s*comment\s*%}/,/\{%\s*endcomment\s*%}/),e.C(/\{#/,/#}/),{cN:"template_tag",b:/\{%/,e:/%}/,k:"comment endcomment load templatetag ifchanged endifchanged if endif firstof for endfor in ifnotequal endifnotequal widthratio extends include spaceless endspaceless regroup by as ifequal endifequal ssi now with cycle url filter endfilter debug block endblock else autoescape endautoescape csrf_token empty elif endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix plural get_current_language language get_available_languages get_current_language_bidi get_language_info get_language_info_list localize endlocalize localtime endlocaltime timezone endtimezone get_current_timezone verbatim",c:[t]},{cN:"variable",b:/\{\{/,e:/}}/,c:[t]}]}});hljs.registerLanguage("verilog",function(e){return{aliases:["v"],cI:!0,k:{keyword:"always and assign begin buf bufif0 bufif1 case casex casez cmos deassign default defparam disable edge else end endcase endfunction endmodule endprimitive endspecify endtable endtask event for force forever fork function if ifnone initial inout input join macromodule module nand negedge nmos nor not notif0 notif1 or output parameter pmos posedge primitive pulldown pullup rcmos release repeat rnmos rpmos rtran rtranif0 rtranif1 specify specparam table task timescale tran tranif0 tranif1 wait while xnor xor",typename:"highz0 highz1 integer large medium pull0 pull1 real realtime reg scalared signed small strong0 strong1 supply0 supply0 supply1 supply1 time tri tri0 tri1 triand trior trireg vectored wand weak0 weak1 wire wor"},c:[e.CBCM,e.CLCM,e.QSM,{cN:"number",b:"\\b(\\d+'(b|h|o|d|B|H|O|D))?[0-9xzXZ]+",c:[e.BE],r:0},{cN:"typename",b:"\\.\\w+",r:0},{cN:"value",b:"#\\((?!parameter).+\\)"},{cN:"keyword",b:"\\+|-|\\*|/|%|<|>|=|#|`|\\!|&|\\||@|:|\\^|~|\\{|\\}",r:0}]}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={cN:"variable",v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},o=[e.BE,r,n],i=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:o,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=i,s.c=i,{aliases:["pl"],k:t,c:i}});hljs.registerLanguage("fsharp",function(e){var t={b:"<",e:">",c:[e.inherit(e.TM,{b:/'[a-zA-Z0-9_]+/})]};return{aliases:["fs"],k:"abstract and as assert base begin class default delegate do done downcast downto elif else end exception extern false finally for fun function global if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield",c:[{cN:"keyword",b:/\b(yield|return|let|do)!/},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},{cN:"string",b:'"""',e:'"""'},e.C("\\(\\*","\\*\\)"),{cN:"class",bK:"type",e:"\\(|=|$",eE:!0,c:[e.UTM,t]},{cN:"annotation",b:"\\[<",e:">\\]",r:10},{cN:"attribute",b:"\\B('[A-Za-z])\\b",c:[e.BE]},e.CLCM,e.inherit(e.QSM,{i:null}),e.CNM]}});hljs.registerLanguage("swift",function(e){var i={keyword:"class deinit enum extension func init let protocol static struct subscript typealias var break case continue default do else fallthrough if in for return switch where while as dynamicType is new super self Self Type __COLUMN__ __FILE__ __FUNCTION__ __LINE__ associativity didSet get infix inout left mutating none nonmutating operator override postfix precedence prefix right set unowned unowned safe unsafe weak willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue assert bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced join lexicographicalCompare map max maxElement min minElement numericCast partition posix print println quickSort reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith strideof strideofValue swap swift toString transcode underestimateCount unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafePointers withVaList"},t={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},s={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0},o=e.inherit(e.QSM,{c:[r,e.BE]});return r.c=[s],{k:i,c:[o,e.CLCM,n,t,s,{cN:"func",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/,i:/\(/}),{cN:"generics",b://,i:/>/},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",s,o,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/})]},{cN:"preprocessor",b:"(@assignment|@class_protocol|@exported|@final|@lazy|@noreturn|@NSCopying|@NSManaged|@objc|@optional|@required|@auto_closure|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix)"},{bK:"import",e:/$/,c:[e.CLCM,n]}]}});hljs.registerLanguage("cs",function(e){var r="abstract as base bool break byte case catch char checked const continue decimal dynamic default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long null when object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async protected public private internal ascending descending from get group into join let orderby partial select set value var where yield",t=e.IR+"(<"+e.IR+">)?";return{aliases:["csharp"],k:r,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"xmlDocTag",v:[{b:"///",r:0},{b:""},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},e.ASM,e.QSM,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[{cN:"title",b:"[a-zA-Z](\\.?\\w)*",r:0},e.CLCM,e.CBCM]},{bK:"new return throw await",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("accesslog",function(T){return{c:[{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+\\b",r:0},{cN:"string",b:'"(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|PATCH|TRACE)',e:'"',k:"GET POST HEAD PUT DELETE CONNECT OPTIONS PATCH TRACE",i:"\\n",r:10},{cN:"string",b:/\[/,e:/\]/,i:"\\n"},{cN:"string",b:'"',e:'"',i:"\\n"}]}});hljs.registerLanguage("r",function(e){var r="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{c:[e.HCM,{b:r,l:r,k:{keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},r:0},{cN:"number",b:"0[xX][0-9a-fA-F]+[Li]?\\b",r:0},{cN:"number",b:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",r:0},{cN:"number",b:"\\d+\\.(?!\\d)(?:i\\b)?",r:0},{cN:"number",b:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{cN:"number",b:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{b:"`",e:"`",r:0},{cN:"string",c:[e.BE],v:[{b:'"',e:'"'},{b:"'",e:"'"}]}]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"tag",b:""},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("http",function(t){return{aliases:["https"],i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("cmake",function(e){return{aliases:["cmake.in"],cI:!0,k:{keyword:"add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_subdirectory add_test aux_source_directory break build_command cmake_minimum_required cmake_policy configure_file create_test_sourcelist define_property else elseif enable_language enable_testing endforeach endfunction endif endmacro endwhile execute_process export find_file find_library find_package find_path find_program fltk_wrap_ui foreach function get_cmake_property get_directory_property get_filename_component get_property get_source_file_property get_target_property get_test_property if include include_directories include_external_msproject include_regular_expression install link_directories load_cache load_command macro mark_as_advanced message option output_required_files project qt_wrap_cpp qt_wrap_ui remove_definitions return separate_arguments set set_directory_properties set_property set_source_files_properties set_target_properties set_tests_properties site_name source_group string target_link_libraries try_compile try_run unset variable_watch while build_name exec_program export_library_dependencies install_files install_programs install_targets link_libraries make_directory remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file qt5_use_modules qt5_use_package qt5_wrap_cpp on off true false and or",operator:"equal less greater strless strgreater strequal matches"},c:[{cN:"envvar",b:"\\${",e:"}"},e.HCM,e.QSM,e.NM]}});hljs.registerLanguage("thrift",function(e){var t="bool byte i16 i32 i64 double string binary";return{k:{keyword:"namespace const typedef struct enum service exception void oneway set list map required optional",built_in:t,literal:"true false"},c:[e.QSM,e.NM,e.CLCM,e.CBCM,{cN:"class",bK:"struct enum service exception",e:/\{/,i:/\n/,c:[e.inherit(e.TM,{starts:{eW:!0,eE:!0}})]},{b:"\\b(set|list|map)\\s*<",e:">",k:t,c:["self"]}]}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},i={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:i,l:o,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:o,c:[e.UTM]},{cN:"variable",b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("mathematica",function(e){return{aliases:["mma"],l:"(\\$|\\b)"+e.IR+"\\b",k:"AbelianGroup Abort AbortKernels AbortProtect Above Abs Absolute AbsoluteCorrelation AbsoluteCorrelationFunction AbsoluteCurrentValue AbsoluteDashing AbsoluteFileName AbsoluteOptions AbsolutePointSize AbsoluteThickness AbsoluteTime AbsoluteTiming AccountingForm Accumulate Accuracy AccuracyGoal ActionDelay ActionMenu ActionMenuBox ActionMenuBoxOptions Active ActiveItem ActiveStyle AcyclicGraphQ AddOnHelpPath AddTo AdjacencyGraph AdjacencyList AdjacencyMatrix AdjustmentBox AdjustmentBoxOptions AdjustTimeSeriesForecast AffineTransform After AiryAi AiryAiPrime AiryAiZero AiryBi AiryBiPrime AiryBiZero AlgebraicIntegerQ AlgebraicNumber AlgebraicNumberDenominator AlgebraicNumberNorm AlgebraicNumberPolynomial AlgebraicNumberTrace AlgebraicRules AlgebraicRulesData Algebraics AlgebraicUnitQ Alignment AlignmentMarker AlignmentPoint All AllowedDimensions AllowGroupClose AllowInlineCells AllowKernelInitialization AllowReverseGroupClose AllowScriptLevelChange AlphaChannel AlternatingGroup AlternativeHypothesis Alternatives AmbientLight Analytic AnchoredSearch And AndersonDarlingTest AngerJ AngleBracket AngularGauge Animate AnimationCycleOffset AnimationCycleRepetitions AnimationDirection AnimationDisplayTime AnimationRate AnimationRepetitions AnimationRunning Animator AnimatorBox AnimatorBoxOptions AnimatorElements Annotation Annuity AnnuityDue Antialiasing Antisymmetric Apart ApartSquareFree Appearance AppearanceElements AppellF1 Append AppendTo Apply ArcCos ArcCosh ArcCot ArcCoth ArcCsc ArcCsch ArcSec ArcSech ArcSin ArcSinDistribution ArcSinh ArcTan ArcTanh Arg ArgMax ArgMin ArgumentCountQ ARIMAProcess ArithmeticGeometricMean ARMAProcess ARProcess Array ArrayComponents ArrayDepth ArrayFlatten ArrayPad ArrayPlot ArrayQ ArrayReshape ArrayRules Arrays Arrow Arrow3DBox ArrowBox Arrowheads AspectRatio AspectRatioFixed Assert Assuming Assumptions AstronomicalData Asynchronous AsynchronousTaskObject AsynchronousTasks AtomQ Attributes AugmentedSymmetricPolynomial AutoAction AutoDelete AutoEvaluateEvents AutoGeneratedPackage AutoIndent AutoIndentSpacings AutoItalicWords AutoloadPath AutoMatch Automatic AutomaticImageSize AutoMultiplicationSymbol AutoNumberFormatting AutoOpenNotebooks AutoOpenPalettes AutorunSequencing AutoScaling AutoScroll AutoSpacing AutoStyleOptions AutoStyleWords Axes AxesEdge AxesLabel AxesOrigin AxesStyle Axis BabyMonsterGroupB Back Background BackgroundTasksSettings Backslash Backsubstitution Backward Band BandpassFilter BandstopFilter BarabasiAlbertGraphDistribution BarChart BarChart3D BarLegend BarlowProschanImportance BarnesG BarOrigin BarSpacing BartlettHannWindow BartlettWindow BaseForm Baseline BaselinePosition BaseStyle BatesDistribution BattleLemarieWavelet Because BeckmannDistribution Beep Before Begin BeginDialogPacket BeginFrontEndInteractionPacket BeginPackage BellB BellY Below BenfordDistribution BeniniDistribution BenktanderGibratDistribution BenktanderWeibullDistribution BernoulliB BernoulliDistribution BernoulliGraphDistribution BernoulliProcess BernsteinBasis BesselFilterModel BesselI BesselJ BesselJZero BesselK BesselY BesselYZero Beta BetaBinomialDistribution BetaDistribution BetaNegativeBinomialDistribution BetaPrimeDistribution BetaRegularized BetweennessCentrality BezierCurve BezierCurve3DBox BezierCurve3DBoxOptions BezierCurveBox BezierCurveBoxOptions BezierFunction BilateralFilter Binarize BinaryFormat BinaryImageQ BinaryRead BinaryReadList BinaryWrite BinCounts BinLists Binomial BinomialDistribution BinomialProcess BinormalDistribution BiorthogonalSplineWavelet BipartiteGraphQ BirnbaumImportance BirnbaumSaundersDistribution BitAnd BitClear BitGet BitLength BitNot BitOr BitSet BitShiftLeft BitShiftRight BitXor Black BlackmanHarrisWindow BlackmanNuttallWindow BlackmanWindow Blank BlankForm BlankNullSequence BlankSequence Blend Block BlockRandom BlomqvistBeta BlomqvistBetaTest Blue Blur BodePlot BohmanWindow Bold Bookmarks Boole BooleanConsecutiveFunction BooleanConvert BooleanCountingFunction BooleanFunction BooleanGraph BooleanMaxterms BooleanMinimize BooleanMinterms Booleans BooleanTable BooleanVariables BorderDimensions BorelTannerDistribution Bottom BottomHatTransform BoundaryStyle Bounds Box BoxBaselineShift BoxData BoxDimensions Boxed Boxes BoxForm BoxFormFormatTypes BoxFrame BoxID BoxMargins BoxMatrix BoxRatios BoxRotation BoxRotationPoint BoxStyle BoxWhiskerChart Bra BracketingBar BraKet BrayCurtisDistance BreadthFirstScan Break Brown BrownForsytheTest BrownianBridgeProcess BrowserCategory BSplineBasis BSplineCurve BSplineCurve3DBox BSplineCurveBox BSplineCurveBoxOptions BSplineFunction BSplineSurface BSplineSurface3DBox BubbleChart BubbleChart3D BubbleScale BubbleSizes BulletGauge BusinessDayQ ButterflyGraph ButterworthFilterModel Button ButtonBar ButtonBox ButtonBoxOptions ButtonCell ButtonContents ButtonData ButtonEvaluator ButtonExpandable ButtonFrame ButtonFunction ButtonMargins ButtonMinHeight ButtonNote ButtonNotebook ButtonSource ButtonStyle ButtonStyleMenuListing Byte ByteCount ByteOrdering C CachedValue CacheGraphics CalendarData CalendarType CallPacket CanberraDistance Cancel CancelButton CandlestickChart Cap CapForm CapitalDifferentialD CardinalBSplineBasis CarmichaelLambda Cases Cashflow Casoratian Catalan CatalanNumber Catch CauchyDistribution CauchyWindow CayleyGraph CDF CDFDeploy CDFInformation CDFWavelet Ceiling Cell CellAutoOverwrite CellBaseline CellBoundingBox CellBracketOptions CellChangeTimes CellContents CellContext CellDingbat CellDynamicExpression CellEditDuplicate CellElementsBoundingBox CellElementSpacings CellEpilog CellEvaluationDuplicate CellEvaluationFunction CellEventActions CellFrame CellFrameColor CellFrameLabelMargins CellFrameLabels CellFrameMargins CellGroup CellGroupData CellGrouping CellGroupingRules CellHorizontalScrolling CellID CellLabel CellLabelAutoDelete CellLabelMargins CellLabelPositioning CellMargins CellObject CellOpen CellPrint CellProlog Cells CellSize CellStyle CellTags CellularAutomaton CensoredDistribution Censoring Center CenterDot CentralMoment CentralMomentGeneratingFunction CForm ChampernowneNumber ChanVeseBinarize Character CharacterEncoding CharacterEncodingsPath CharacteristicFunction CharacteristicPolynomial CharacterRange Characters ChartBaseStyle ChartElementData ChartElementDataFunction ChartElementFunction ChartElements ChartLabels ChartLayout ChartLegends ChartStyle Chebyshev1FilterModel Chebyshev2FilterModel ChebyshevDistance ChebyshevT ChebyshevU Check CheckAbort CheckAll Checkbox CheckboxBar CheckboxBox CheckboxBoxOptions ChemicalData ChessboardDistance ChiDistribution ChineseRemainder ChiSquareDistribution ChoiceButtons ChoiceDialog CholeskyDecomposition Chop Circle CircleBox CircleDot CircleMinus CirclePlus CircleTimes CirculantGraph CityData Clear ClearAll ClearAttributes ClearSystemCache ClebschGordan ClickPane Clip ClipboardNotebook ClipFill ClippingStyle ClipPlanes ClipRange Clock ClockGauge ClockwiseContourIntegral Close Closed CloseKernels ClosenessCentrality Closing ClosingAutoSave ClosingEvent ClusteringComponents CMYKColor Coarse Coefficient CoefficientArrays CoefficientDomain CoefficientList CoefficientRules CoifletWavelet Collect Colon ColonForm ColorCombine ColorConvert ColorData ColorDataFunction ColorFunction ColorFunctionScaling Colorize ColorNegate ColorOutput ColorProfileData ColorQuantize ColorReplace ColorRules ColorSelectorSettings ColorSeparate ColorSetter ColorSetterBox ColorSetterBoxOptions ColorSlider ColorSpace Column ColumnAlignments ColumnBackgrounds ColumnForm ColumnLines ColumnsEqual ColumnSpacings ColumnWidths CommonDefaultFormatTypes Commonest CommonestFilter CommonUnits CommunityBoundaryStyle CommunityGraphPlot CommunityLabels CommunityRegionStyle CompatibleUnitQ CompilationOptions CompilationTarget Compile Compiled CompiledFunction Complement CompleteGraph CompleteGraphQ CompleteKaryTree CompletionsListPacket Complex Complexes ComplexExpand ComplexInfinity ComplexityFunction ComponentMeasurements ComponentwiseContextMenu Compose ComposeList ComposeSeries Composition CompoundExpression CompoundPoissonDistribution CompoundPoissonProcess CompoundRenewalProcess Compress CompressedData Condition ConditionalExpression Conditioned Cone ConeBox ConfidenceLevel ConfidenceRange ConfidenceTransform ConfigurationPath Congruent Conjugate ConjugateTranspose Conjunction Connect ConnectedComponents ConnectedGraphQ ConnesWindow ConoverTest ConsoleMessage ConsoleMessagePacket ConsolePrint Constant ConstantArray Constants ConstrainedMax ConstrainedMin ContentPadding ContentsBoundingBox ContentSelectable ContentSize Context ContextMenu Contexts ContextToFilename ContextToFileName Continuation Continue ContinuedFraction ContinuedFractionK ContinuousAction ContinuousMarkovProcess ContinuousTimeModelQ ContinuousWaveletData ContinuousWaveletTransform ContourDetect ContourGraphics ContourIntegral ContourLabels ContourLines ContourPlot ContourPlot3D Contours ContourShading ContourSmoothing ContourStyle ContraharmonicMean Control ControlActive ControlAlignment ControllabilityGramian ControllabilityMatrix ControllableDecomposition ControllableModelQ ControllerDuration ControllerInformation ControllerInformationData ControllerLinking ControllerManipulate ControllerMethod ControllerPath ControllerState ControlPlacement ControlsRendering ControlType Convergents ConversionOptions ConversionRules ConvertToBitmapPacket ConvertToPostScript ConvertToPostScriptPacket Convolve ConwayGroupCo1 ConwayGroupCo2 ConwayGroupCo3 CoordinateChartData CoordinatesToolOptions CoordinateTransform CoordinateTransformData CoprimeQ Coproduct CopulaDistribution Copyable CopyDirectory CopyFile CopyTag CopyToClipboard CornerFilter CornerNeighbors Correlation CorrelationDistance CorrelationFunction CorrelationTest Cos Cosh CoshIntegral CosineDistance CosineWindow CosIntegral Cot Coth Count CounterAssignments CounterBox CounterBoxOptions CounterClockwiseContourIntegral CounterEvaluator CounterFunction CounterIncrements CounterStyle CounterStyleMenuListing CountRoots CountryData Covariance CovarianceEstimatorFunction CovarianceFunction CoxianDistribution CoxIngersollRossProcess CoxModel CoxModelFit CramerVonMisesTest CreateArchive CreateDialog CreateDirectory CreateDocument CreateIntermediateDirectories CreatePalette CreatePalettePacket CreateScheduledTask CreateTemporary CreateWindow CriticalityFailureImportance CriticalitySuccessImportance CriticalSection Cross CrossingDetect CrossMatrix Csc Csch CubeRoot Cubics Cuboid CuboidBox Cumulant CumulantGeneratingFunction Cup CupCap Curl CurlyDoubleQuote CurlyQuote CurrentImage CurrentlySpeakingPacket CurrentValue CurvatureFlowFilter CurveClosed Cyan CycleGraph CycleIndexPolynomial Cycles CyclicGroup Cyclotomic Cylinder CylinderBox CylindricalDecomposition D DagumDistribution DamerauLevenshteinDistance DampingFactor Darker Dashed Dashing DataCompression DataDistribution DataRange DataReversed Date DateDelimiters DateDifference DateFunction DateList DateListLogPlot DateListPlot DatePattern DatePlus DateRange DateString DateTicksFormat DaubechiesWavelet DavisDistribution DawsonF DayCount DayCountConvention DayMatchQ DayName DayPlus DayRange DayRound DeBruijnGraph Debug DebugTag Decimal DeclareKnownSymbols DeclarePackage Decompose Decrement DedekindEta Default DefaultAxesStyle DefaultBaseStyle DefaultBoxStyle DefaultButton DefaultColor DefaultControlPlacement DefaultDuplicateCellStyle DefaultDuration DefaultElement DefaultFaceGridsStyle DefaultFieldHintStyle DefaultFont DefaultFontProperties DefaultFormatType DefaultFormatTypeForStyle DefaultFrameStyle DefaultFrameTicksStyle DefaultGridLinesStyle DefaultInlineFormatType DefaultInputFormatType DefaultLabelStyle DefaultMenuStyle DefaultNaturalLanguage DefaultNewCellStyle DefaultNewInlineCellStyle DefaultNotebook DefaultOptions DefaultOutputFormatType DefaultStyle DefaultStyleDefinitions DefaultTextFormatType DefaultTextInlineFormatType DefaultTicksStyle DefaultTooltipStyle DefaultValues Defer DefineExternal DefineInputStreamMethod DefineOutputStreamMethod Definition Degree DegreeCentrality DegreeGraphDistribution DegreeLexicographic DegreeReverseLexicographic Deinitialization Del Deletable Delete DeleteBorderComponents DeleteCases DeleteContents DeleteDirectory DeleteDuplicates DeleteFile DeleteSmallComponents DeleteWithContents DeletionWarning Delimiter DelimiterFlashTime DelimiterMatching Delimiters Denominator DensityGraphics DensityHistogram DensityPlot DependentVariables Deploy Deployed Depth DepthFirstScan Derivative DerivativeFilter DescriptorStateSpace DesignMatrix Det DGaussianWavelet DiacriticalPositioning Diagonal DiagonalMatrix Dialog DialogIndent DialogInput DialogLevel DialogNotebook DialogProlog DialogReturn DialogSymbols Diamond DiamondMatrix DiceDissimilarity DictionaryLookup DifferenceDelta DifferenceOrder DifferenceRoot DifferenceRootReduce Differences DifferentialD DifferentialRoot DifferentialRootReduce DifferentiatorFilter DigitBlock DigitBlockMinimum DigitCharacter DigitCount DigitQ DihedralGroup Dilation Dimensions DiracComb DiracDelta DirectedEdge DirectedEdges DirectedGraph DirectedGraphQ DirectedInfinity Direction Directive Directory DirectoryName DirectoryQ DirectoryStack DirichletCharacter DirichletConvolve DirichletDistribution DirichletL DirichletTransform DirichletWindow DisableConsolePrintPacket DiscreteChirpZTransform DiscreteConvolve DiscreteDelta DiscreteHadamardTransform DiscreteIndicator DiscreteLQEstimatorGains DiscreteLQRegulatorGains DiscreteLyapunovSolve DiscreteMarkovProcess DiscretePlot DiscretePlot3D DiscreteRatio DiscreteRiccatiSolve DiscreteShift DiscreteTimeModelQ DiscreteUniformDistribution DiscreteVariables DiscreteWaveletData DiscreteWaveletPacketTransform DiscreteWaveletTransform Discriminant Disjunction Disk DiskBox DiskMatrix Dispatch DispersionEstimatorFunction Display DisplayAllSteps DisplayEndPacket DisplayFlushImagePacket DisplayForm DisplayFunction DisplayPacket DisplayRules DisplaySetSizePacket DisplayString DisplayTemporary DisplayWith DisplayWithRef DisplayWithVariable DistanceFunction DistanceTransform Distribute Distributed DistributedContexts DistributeDefinitions DistributionChart DistributionDomain DistributionFitTest DistributionParameterAssumptions DistributionParameterQ Dithering Div Divergence Divide DivideBy Dividers Divisible Divisors DivisorSigma DivisorSum DMSList DMSString Do DockedCells DocumentNotebook DominantColors DOSTextFormat Dot DotDashed DotEqual Dotted DoubleBracketingBar DoubleContourIntegral DoubleDownArrow DoubleLeftArrow DoubleLeftRightArrow DoubleLeftTee DoubleLongLeftArrow DoubleLongLeftRightArrow DoubleLongRightArrow DoubleRightArrow DoubleRightTee DoubleUpArrow DoubleUpDownArrow DoubleVerticalBar DoublyInfinite Down DownArrow DownArrowBar DownArrowUpArrow DownLeftRightVector DownLeftTeeVector DownLeftVector DownLeftVectorBar DownRightTeeVector DownRightVector DownRightVectorBar Downsample DownTee DownTeeArrow DownValues DragAndDrop DrawEdges DrawFrontFaces DrawHighlighted Drop DSolve Dt DualLinearProgramming DualSystemsModel DumpGet DumpSave DuplicateFreeQ Dynamic DynamicBox DynamicBoxOptions DynamicEvaluationTimeout DynamicLocation DynamicModule DynamicModuleBox DynamicModuleBoxOptions DynamicModuleParent DynamicModuleValues DynamicName DynamicNamespace DynamicReference DynamicSetting DynamicUpdating DynamicWrapper DynamicWrapperBox DynamicWrapperBoxOptions E EccentricityCentrality EdgeAdd EdgeBetweennessCentrality EdgeCapacity EdgeCapForm EdgeColor EdgeConnectivity EdgeCost EdgeCount EdgeCoverQ EdgeDashing EdgeDelete EdgeDetect EdgeForm EdgeIndex EdgeJoinForm EdgeLabeling EdgeLabels EdgeLabelStyle EdgeList EdgeOpacity EdgeQ EdgeRenderingFunction EdgeRules EdgeShapeFunction EdgeStyle EdgeThickness EdgeWeight Editable EditButtonSettings EditCellTagsSettings EditDistance EffectiveInterest Eigensystem Eigenvalues EigenvectorCentrality Eigenvectors Element ElementData Eliminate EliminationOrder EllipticE EllipticExp EllipticExpPrime EllipticF EllipticFilterModel EllipticK EllipticLog EllipticNomeQ EllipticPi EllipticReducedHalfPeriods EllipticTheta EllipticThetaPrime EmitSound EmphasizeSyntaxErrors EmpiricalDistribution Empty EmptyGraphQ EnableConsolePrintPacket Enabled Encode End EndAdd EndDialogPacket EndFrontEndInteractionPacket EndOfFile EndOfLine EndOfString EndPackage EngineeringForm Enter EnterExpressionPacket EnterTextPacket Entropy EntropyFilter Environment Epilog Equal EqualColumns EqualRows EqualTilde EquatedTo Equilibrium EquirippleFilterKernel Equivalent Erf Erfc Erfi ErlangB ErlangC ErlangDistribution Erosion ErrorBox ErrorBoxOptions ErrorNorm ErrorPacket ErrorsDialogSettings EstimatedDistribution EstimatedProcess EstimatorGains EstimatorRegulator EuclideanDistance EulerE EulerGamma EulerianGraphQ EulerPhi Evaluatable Evaluate Evaluated EvaluatePacket EvaluationCell EvaluationCompletionAction EvaluationElements EvaluationMode EvaluationMonitor EvaluationNotebook EvaluationObject EvaluationOrder Evaluator EvaluatorNames EvenQ EventData EventEvaluator EventHandler EventHandlerTag EventLabels ExactBlackmanWindow ExactNumberQ ExactRootIsolation ExampleData Except ExcludedForms ExcludePods Exclusions ExclusionsStyle Exists Exit ExitDialog Exp Expand ExpandAll ExpandDenominator ExpandFileName ExpandNumerator Expectation ExpectationE ExpectedValue ExpGammaDistribution ExpIntegralE ExpIntegralEi Exponent ExponentFunction ExponentialDistribution ExponentialFamily ExponentialGeneratingFunction ExponentialMovingAverage ExponentialPowerDistribution ExponentPosition ExponentStep Export ExportAutoReplacements ExportPacket ExportString Expression ExpressionCell ExpressionPacket ExpToTrig ExtendedGCD Extension ExtentElementFunction ExtentMarkers ExtentSize ExternalCall ExternalDataCharacterEncoding Extract ExtractArchive ExtremeValueDistribution FaceForm FaceGrids FaceGridsStyle Factor FactorComplete Factorial Factorial2 FactorialMoment FactorialMomentGeneratingFunction FactorialPower FactorInteger FactorList FactorSquareFree FactorSquareFreeList FactorTerms FactorTermsList Fail FailureDistribution False FARIMAProcess FEDisableConsolePrintPacket FeedbackSector FeedbackSectorStyle FeedbackType FEEnableConsolePrintPacket Fibonacci FieldHint FieldHintStyle FieldMasked FieldSize File FileBaseName FileByteCount FileDate FileExistsQ FileExtension FileFormat FileHash FileInformation FileName FileNameDepth FileNameDialogSettings FileNameDrop FileNameJoin FileNames FileNameSetter FileNameSplit FileNameTake FilePrint FileType FilledCurve FilledCurveBox Filling FillingStyle FillingTransform FilterRules FinancialBond FinancialData FinancialDerivative FinancialIndicator Find FindArgMax FindArgMin FindClique FindClusters FindCurvePath FindDistributionParameters FindDivisions FindEdgeCover FindEdgeCut FindEulerianCycle FindFaces FindFile FindFit FindGeneratingFunction FindGeoLocation FindGeometricTransform FindGraphCommunities FindGraphIsomorphism FindGraphPartition FindHamiltonianCycle FindIndependentEdgeSet FindIndependentVertexSet FindInstance FindIntegerNullVector FindKClan FindKClique FindKClub FindKPlex FindLibrary FindLinearRecurrence FindList FindMaximum FindMaximumFlow FindMaxValue FindMinimum FindMinimumCostFlow FindMinimumCut FindMinValue FindPermutation FindPostmanTour FindProcessParameters FindRoot FindSequenceFunction FindSettings FindShortestPath FindShortestTour FindThreshold FindVertexCover FindVertexCut Fine FinishDynamic FiniteAbelianGroupCount FiniteGroupCount FiniteGroupData First FirstPassageTimeDistribution FischerGroupFi22 FischerGroupFi23 FischerGroupFi24Prime FisherHypergeometricDistribution FisherRatioTest FisherZDistribution Fit FitAll FittedModel FixedPoint FixedPointList FlashSelection Flat Flatten FlattenAt FlatTopWindow FlipView Floor FlushPrintOutputPacket Fold FoldList Font FontColor FontFamily FontForm FontName FontOpacity FontPostScriptName FontProperties FontReencoding FontSize FontSlant FontSubstitutions FontTracking FontVariations FontWeight For ForAll Format FormatRules FormatType FormatTypeAutoConvert FormatValues FormBox FormBoxOptions FortranForm Forward ForwardBackward Fourier FourierCoefficient FourierCosCoefficient FourierCosSeries FourierCosTransform FourierDCT FourierDCTFilter FourierDCTMatrix FourierDST FourierDSTMatrix FourierMatrix FourierParameters FourierSequenceTransform FourierSeries FourierSinCoefficient FourierSinSeries FourierSinTransform FourierTransform FourierTrigSeries FractionalBrownianMotionProcess FractionalPart FractionBox FractionBoxOptions FractionLine Frame FrameBox FrameBoxOptions Framed FrameInset FrameLabel Frameless FrameMargins FrameStyle FrameTicks FrameTicksStyle FRatioDistribution FrechetDistribution FreeQ FrequencySamplingFilterKernel FresnelC FresnelS Friday FrobeniusNumber FrobeniusSolve FromCharacterCode FromCoefficientRules FromContinuedFraction FromDate FromDigits FromDMS Front FrontEndDynamicExpression FrontEndEventActions FrontEndExecute FrontEndObject FrontEndResource FrontEndResourceString FrontEndStackSize FrontEndToken FrontEndTokenExecute FrontEndValueCache FrontEndVersion FrontFaceColor FrontFaceOpacity Full FullAxes FullDefinition FullForm FullGraphics FullOptions FullSimplify Function FunctionExpand FunctionInterpolation FunctionSpace FussellVeselyImportance GaborFilter GaborMatrix GaborWavelet GainMargins GainPhaseMargins Gamma GammaDistribution GammaRegularized GapPenalty Gather GatherBy GaugeFaceElementFunction GaugeFaceStyle GaugeFrameElementFunction GaugeFrameSize GaugeFrameStyle GaugeLabels GaugeMarkers GaugeStyle GaussianFilter GaussianIntegers GaussianMatrix GaussianWindow GCD GegenbauerC General GeneralizedLinearModelFit GenerateConditions GeneratedCell GeneratedParameters GeneratingFunction Generic GenericCylindricalDecomposition GenomeData GenomeLookup GeodesicClosing GeodesicDilation GeodesicErosion GeodesicOpening GeoDestination GeodesyData GeoDirection GeoDistance GeoGridPosition GeometricBrownianMotionProcess GeometricDistribution GeometricMean GeometricMeanFilter GeometricTransformation GeometricTransformation3DBox GeometricTransformation3DBoxOptions GeometricTransformationBox GeometricTransformationBoxOptions GeoPosition GeoPositionENU GeoPositionXYZ GeoProjectionData GestureHandler GestureHandlerTag Get GetBoundingBoxSizePacket GetContext GetEnvironment GetFileName GetFrontEndOptionsDataPacket GetLinebreakInformationPacket GetMenusPacket GetPageBreakInformationPacket Glaisher GlobalClusteringCoefficient GlobalPreferences GlobalSession Glow GoldenRatio GompertzMakehamDistribution GoodmanKruskalGamma GoodmanKruskalGammaTest Goto Grad Gradient GradientFilter GradientOrientationFilter Graph GraphAssortativity GraphCenter GraphComplement GraphData GraphDensity GraphDiameter GraphDifference GraphDisjointUnion GraphDistance GraphDistanceMatrix GraphElementData GraphEmbedding GraphHighlight GraphHighlightStyle GraphHub Graphics Graphics3D Graphics3DBox Graphics3DBoxOptions GraphicsArray GraphicsBaseline GraphicsBox GraphicsBoxOptions GraphicsColor GraphicsColumn GraphicsComplex GraphicsComplex3DBox GraphicsComplex3DBoxOptions GraphicsComplexBox GraphicsComplexBoxOptions GraphicsContents GraphicsData GraphicsGrid GraphicsGridBox GraphicsGroup GraphicsGroup3DBox GraphicsGroup3DBoxOptions GraphicsGroupBox GraphicsGroupBoxOptions GraphicsGrouping GraphicsHighlightColor GraphicsRow GraphicsSpacing GraphicsStyle GraphIntersection GraphLayout GraphLinkEfficiency GraphPeriphery GraphPlot GraphPlot3D GraphPower GraphPropertyDistribution GraphQ GraphRadius GraphReciprocity GraphRoot GraphStyle GraphUnion Gray GrayLevel GreatCircleDistance Greater GreaterEqual GreaterEqualLess GreaterFullEqual GreaterGreater GreaterLess GreaterSlantEqual GreaterTilde Green Grid GridBaseline GridBox GridBoxAlignment GridBoxBackground GridBoxDividers GridBoxFrame GridBoxItemSize GridBoxItemStyle GridBoxOptions GridBoxSpacings GridCreationSettings GridDefaultElement GridElementStyleOptions GridFrame GridFrameMargins GridGraph GridLines GridLinesStyle GroebnerBasis GroupActionBase GroupCentralizer GroupElementFromWord GroupElementPosition GroupElementQ GroupElements GroupElementToWord GroupGenerators GroupMultiplicationTable GroupOrbits GroupOrder GroupPageBreakWithin GroupSetwiseStabilizer GroupStabilizer GroupStabilizerChain Gudermannian GumbelDistribution HaarWavelet HadamardMatrix HalfNormalDistribution HamiltonianGraphQ HammingDistance HammingWindow HankelH1 HankelH2 HankelMatrix HannPoissonWindow HannWindow HaradaNortonGroupHN HararyGraph HarmonicMean HarmonicMeanFilter HarmonicNumber Hash HashTable Haversine HazardFunction Head HeadCompose Heads HeavisideLambda HeavisidePi HeavisideTheta HeldGroupHe HeldPart HelpBrowserLookup HelpBrowserNotebook HelpBrowserSettings HermiteDecomposition HermiteH HermitianMatrixQ HessenbergDecomposition Hessian HexadecimalCharacter Hexahedron HexahedronBox HexahedronBoxOptions HiddenSurface HighlightGraph HighlightImage HighpassFilter HigmanSimsGroupHS HilbertFilter HilbertMatrix Histogram Histogram3D HistogramDistribution HistogramList HistogramTransform HistogramTransformInterpolation HitMissTransform HITSCentrality HodgeDual HoeffdingD HoeffdingDTest Hold HoldAll HoldAllComplete HoldComplete HoldFirst HoldForm HoldPattern HoldRest HolidayCalendar HomeDirectory HomePage Horizontal HorizontalForm HorizontalGauge HorizontalScrollPosition HornerForm HotellingTSquareDistribution HoytDistribution HTMLSave Hue HumpDownHump HumpEqual HurwitzLerchPhi HurwitzZeta HyperbolicDistribution HypercubeGraph HyperexponentialDistribution Hyperfactorial Hypergeometric0F1 Hypergeometric0F1Regularized Hypergeometric1F1 Hypergeometric1F1Regularized Hypergeometric2F1 Hypergeometric2F1Regularized HypergeometricDistribution HypergeometricPFQ HypergeometricPFQRegularized HypergeometricU Hyperlink HyperlinkCreationSettings Hyphenation HyphenationOptions HypoexponentialDistribution HypothesisTestData I Identity IdentityMatrix If IgnoreCase Im Image Image3D Image3DSlices ImageAccumulate ImageAdd ImageAdjust ImageAlign ImageApply ImageAspectRatio ImageAssemble ImageCache ImageCacheValid ImageCapture ImageChannels ImageClip ImageColorSpace ImageCompose ImageConvolve ImageCooccurrence ImageCorners ImageCorrelate ImageCorrespondingPoints ImageCrop ImageData ImageDataPacket ImageDeconvolve ImageDemosaic ImageDifference ImageDimensions ImageDistance ImageEffect ImageFeatureTrack ImageFileApply ImageFileFilter ImageFileScan ImageFilter ImageForestingComponents ImageForwardTransformation ImageHistogram ImageKeypoints ImageLevels ImageLines ImageMargins ImageMarkers ImageMeasurements ImageMultiply ImageOffset ImagePad ImagePadding ImagePartition ImagePeriodogram ImagePerspectiveTransformation ImageQ ImageRangeCache ImageReflect ImageRegion ImageResize ImageResolution ImageRotate ImageRotated ImageScaled ImageScan ImageSize ImageSizeAction ImageSizeCache ImageSizeMultipliers ImageSizeRaw ImageSubtract ImageTake ImageTransformation ImageTrim ImageType ImageValue ImageValuePositions Implies Import ImportAutoReplacements ImportString ImprovementImportance In IncidenceGraph IncidenceList IncidenceMatrix IncludeConstantBasis IncludeFileExtension IncludePods IncludeSingularTerm Increment Indent IndentingNewlineSpacings IndentMaxFraction IndependenceTest IndependentEdgeSetQ IndependentUnit IndependentVertexSetQ Indeterminate IndexCreationOptions Indexed IndexGraph IndexTag Inequality InexactNumberQ InexactNumbers Infinity Infix Information Inherited InheritScope Initialization InitializationCell InitializationCellEvaluation InitializationCellWarning InlineCounterAssignments InlineCounterIncrements InlineRules Inner Inpaint Input InputAliases InputAssumptions InputAutoReplacements InputField InputFieldBox InputFieldBoxOptions InputForm InputGrouping InputNamePacket InputNotebook InputPacket InputSettings InputStream InputString InputStringPacket InputToBoxFormPacket Insert InsertionPointObject InsertResults Inset Inset3DBox Inset3DBoxOptions InsetBox InsetBoxOptions Install InstallService InString Integer IntegerDigits IntegerExponent IntegerLength IntegerPart IntegerPartitions IntegerQ Integers IntegerString Integral Integrate Interactive InteractiveTradingChart Interlaced Interleaving InternallyBalancedDecomposition InterpolatingFunction InterpolatingPolynomial Interpolation InterpolationOrder InterpolationPoints InterpolationPrecision Interpretation InterpretationBox InterpretationBoxOptions InterpretationFunction InterpretTemplate InterquartileRange Interrupt InterruptSettings Intersection Interval IntervalIntersection IntervalMemberQ IntervalUnion Inverse InverseBetaRegularized InverseCDF InverseChiSquareDistribution InverseContinuousWaveletTransform InverseDistanceTransform InverseEllipticNomeQ InverseErf InverseErfc InverseFourier InverseFourierCosTransform InverseFourierSequenceTransform InverseFourierSinTransform InverseFourierTransform InverseFunction InverseFunctions InverseGammaDistribution InverseGammaRegularized InverseGaussianDistribution InverseGudermannian InverseHaversine InverseJacobiCD InverseJacobiCN InverseJacobiCS InverseJacobiDC InverseJacobiDN InverseJacobiDS InverseJacobiNC InverseJacobiND InverseJacobiNS InverseJacobiSC InverseJacobiSD InverseJacobiSN InverseLaplaceTransform InversePermutation InverseRadon InverseSeries InverseSurvivalFunction InverseWaveletTransform InverseWeierstrassP InverseZTransform Invisible InvisibleApplication InvisibleTimes IrreduciblePolynomialQ IsolatingInterval IsomorphicGraphQ IsotopeData Italic Item ItemBox ItemBoxOptions ItemSize ItemStyle ItoProcess JaccardDissimilarity JacobiAmplitude Jacobian JacobiCD JacobiCN JacobiCS JacobiDC JacobiDN JacobiDS JacobiNC JacobiND JacobiNS JacobiP JacobiSC JacobiSD JacobiSN JacobiSymbol JacobiZeta JankoGroupJ1 JankoGroupJ2 JankoGroupJ3 JankoGroupJ4 JarqueBeraALMTest JohnsonDistribution Join Joined JoinedCurve JoinedCurveBox JoinForm JordanDecomposition JordanModelDecomposition K KagiChart KaiserBesselWindow KaiserWindow KalmanEstimator KalmanFilter KarhunenLoeveDecomposition KaryTree KatzCentrality KCoreComponents KDistribution KelvinBei KelvinBer KelvinKei KelvinKer KendallTau KendallTauTest KernelExecute KernelMixtureDistribution KernelObject Kernels Ket Khinchin KirchhoffGraph KirchhoffMatrix KleinInvariantJ KnightTourGraph KnotData KnownUnitQ KolmogorovSmirnovTest KroneckerDelta KroneckerModelDecomposition KroneckerProduct KroneckerSymbol KuiperTest KumaraswamyDistribution Kurtosis KuwaharaFilter Label Labeled LabeledSlider LabelingFunction LabelStyle LaguerreL LambdaComponents LambertW LanczosWindow LandauDistribution Language LanguageCategory LaplaceDistribution LaplaceTransform Laplacian LaplacianFilter LaplacianGaussianFilter Large Larger Last Latitude LatitudeLongitude LatticeData LatticeReduce Launch LaunchKernels LayeredGraphPlot LayerSizeFunction LayoutInformation LCM LeafCount LeapYearQ LeastSquares LeastSquaresFilterKernel Left LeftArrow LeftArrowBar LeftArrowRightArrow LeftDownTeeVector LeftDownVector LeftDownVectorBar LeftRightArrow LeftRightVector LeftTee LeftTeeArrow LeftTeeVector LeftTriangle LeftTriangleBar LeftTriangleEqual LeftUpDownVector LeftUpTeeVector LeftUpVector LeftUpVectorBar LeftVector LeftVectorBar LegendAppearance Legended LegendFunction LegendLabel LegendLayout LegendMargins LegendMarkers LegendMarkerSize LegendreP LegendreQ LegendreType Length LengthWhile LerchPhi Less LessEqual LessEqualGreater LessFullEqual LessGreater LessLess LessSlantEqual LessTilde LetterCharacter LetterQ Level LeveneTest LeviCivitaTensor LevyDistribution Lexicographic LibraryFunction LibraryFunctionError LibraryFunctionInformation LibraryFunctionLoad LibraryFunctionUnload LibraryLoad LibraryUnload LicenseID LiftingFilterData LiftingWaveletTransform LightBlue LightBrown LightCyan Lighter LightGray LightGreen Lighting LightingAngle LightMagenta LightOrange LightPink LightPurple LightRed LightSources LightYellow Likelihood Limit LimitsPositioning LimitsPositioningTokens LindleyDistribution Line Line3DBox LinearFilter LinearFractionalTransform LinearModelFit LinearOffsetFunction LinearProgramming LinearRecurrence LinearSolve LinearSolveFunction LineBox LineBreak LinebreakAdjustments LineBreakChart LineBreakWithin LineColor LineForm LineGraph LineIndent LineIndentMaxFraction LineIntegralConvolutionPlot LineIntegralConvolutionScale LineLegend LineOpacity LineSpacing LineWrapParts LinkActivate LinkClose LinkConnect LinkConnectedQ LinkCreate LinkError LinkFlush LinkFunction LinkHost LinkInterrupt LinkLaunch LinkMode LinkObject LinkOpen LinkOptions LinkPatterns LinkProtocol LinkRead LinkReadHeld LinkReadyQ Links LinkWrite LinkWriteHeld LiouvilleLambda List Listable ListAnimate ListContourPlot ListContourPlot3D ListConvolve ListCorrelate ListCurvePathPlot ListDeconvolve ListDensityPlot Listen ListFourierSequenceTransform ListInterpolation ListLineIntegralConvolutionPlot ListLinePlot ListLogLinearPlot ListLogLogPlot ListLogPlot ListPicker ListPickerBox ListPickerBoxBackground ListPickerBoxOptions ListPlay ListPlot ListPlot3D ListPointPlot3D ListPolarPlot ListQ ListStreamDensityPlot ListStreamPlot ListSurfacePlot3D ListVectorDensityPlot ListVectorPlot ListVectorPlot3D ListZTransform Literal LiteralSearch LocalClusteringCoefficient LocalizeVariables LocationEquivalenceTest LocationTest Locator LocatorAutoCreate LocatorBox LocatorBoxOptions LocatorCentering LocatorPane LocatorPaneBox LocatorPaneBoxOptions LocatorRegion Locked Log Log10 Log2 LogBarnesG LogGamma LogGammaDistribution LogicalExpand LogIntegral LogisticDistribution LogitModelFit LogLikelihood LogLinearPlot LogLogisticDistribution LogLogPlot LogMultinormalDistribution LogNormalDistribution LogPlot LogRankTest LogSeriesDistribution LongEqual Longest LongestAscendingSequence LongestCommonSequence LongestCommonSequencePositions LongestCommonSubsequence LongestCommonSubsequencePositions LongestMatch LongForm Longitude LongLeftArrow LongLeftRightArrow LongRightArrow Loopback LoopFreeGraphQ LowerCaseQ LowerLeftArrow LowerRightArrow LowerTriangularize LowpassFilter LQEstimatorGains LQGRegulator LQOutputRegulatorGains LQRegulatorGains LUBackSubstitution LucasL LuccioSamiComponents LUDecomposition LyapunovSolve LyonsGroupLy MachineID MachineName MachineNumberQ MachinePrecision MacintoshSystemPageSetup Magenta Magnification Magnify MainSolve MaintainDynamicCaches Majority MakeBoxes MakeExpression MakeRules MangoldtLambda ManhattanDistance Manipulate Manipulator MannWhitneyTest MantissaExponent Manual Map MapAll MapAt MapIndexed MAProcess MapThread MarcumQ MardiaCombinedTest MardiaKurtosisTest MardiaSkewnessTest MarginalDistribution MarkovProcessProperties Masking MatchingDissimilarity MatchLocalNameQ MatchLocalNames MatchQ Material MathematicaNotation MathieuC MathieuCharacteristicA MathieuCharacteristicB MathieuCharacteristicExponent MathieuCPrime MathieuGroupM11 MathieuGroupM12 MathieuGroupM22 MathieuGroupM23 MathieuGroupM24 MathieuS MathieuSPrime MathMLForm MathMLText Matrices MatrixExp MatrixForm MatrixFunction MatrixLog MatrixPlot MatrixPower MatrixQ MatrixRank Max MaxBend MaxDetect MaxExtraBandwidths MaxExtraConditions MaxFeatures MaxFilter Maximize MaxIterations MaxMemoryUsed MaxMixtureKernels MaxPlotPoints MaxPoints MaxRecursion MaxStableDistribution MaxStepFraction MaxSteps MaxStepSize MaxValue MaxwellDistribution McLaughlinGroupMcL Mean MeanClusteringCoefficient MeanDegreeConnectivity MeanDeviation MeanFilter MeanGraphDistance MeanNeighborDegree MeanShift MeanShiftFilter Median MedianDeviation MedianFilter Medium MeijerG MeixnerDistribution MemberQ MemoryConstrained MemoryInUse Menu MenuAppearance MenuCommandKey MenuEvaluator MenuItem MenuPacket MenuSortingValue MenuStyle MenuView MergeDifferences Mesh MeshFunctions MeshRange MeshShading MeshStyle Message MessageDialog MessageList MessageName MessageOptions MessagePacket Messages MessagesNotebook MetaCharacters MetaInformation Method MethodOptions MexicanHatWavelet MeyerWavelet Min MinDetect MinFilter MinimalPolynomial MinimalStateSpaceModel Minimize Minors MinRecursion MinSize MinStableDistribution Minus MinusPlus MinValue Missing MissingDataMethod MittagLefflerE MixedRadix MixedRadixQuantity MixtureDistribution Mod Modal Mode Modular ModularLambda Module Modulus MoebiusMu Moment Momentary MomentConvert MomentEvaluate MomentGeneratingFunction Monday Monitor MonomialList MonomialOrder MonsterGroupM MorletWavelet MorphologicalBinarize MorphologicalBranchPoints MorphologicalComponents MorphologicalEulerNumber MorphologicalGraph MorphologicalPerimeter MorphologicalTransform Most MouseAnnotation MouseAppearance MouseAppearanceTag MouseButtons Mouseover MousePointerNote MousePosition MovingAverage MovingMedian MoyalDistribution MultiedgeStyle MultilaunchWarning MultiLetterItalics MultiLetterStyle MultilineFunction Multinomial MultinomialDistribution MultinormalDistribution MultiplicativeOrder Multiplicity Multiselection MultivariateHypergeometricDistribution MultivariatePoissonDistribution MultivariateTDistribution N NakagamiDistribution NameQ Names NamespaceBox Nand NArgMax NArgMin NBernoulliB NCache NDSolve NDSolveValue Nearest NearestFunction NeedCurrentFrontEndPackagePacket NeedCurrentFrontEndSymbolsPacket NeedlemanWunschSimilarity Needs Negative NegativeBinomialDistribution NegativeMultinomialDistribution NeighborhoodGraph Nest NestedGreaterGreater NestedLessLess NestedScriptRules NestList NestWhile NestWhileList NevilleThetaC NevilleThetaD NevilleThetaN NevilleThetaS NewPrimitiveStyle NExpectation Next NextPrime NHoldAll NHoldFirst NHoldRest NicholsGridLines NicholsPlot NIntegrate NMaximize NMaxValue NMinimize NMinValue NominalVariables NonAssociative NoncentralBetaDistribution NoncentralChiSquareDistribution NoncentralFRatioDistribution NoncentralStudentTDistribution NonCommutativeMultiply NonConstants None NonlinearModelFit NonlocalMeansFilter NonNegative NonPositive Nor NorlundB Norm Normal NormalDistribution NormalGrouping Normalize NormalizedSquaredEuclideanDistance NormalsFunction NormFunction Not NotCongruent NotCupCap NotDoubleVerticalBar Notebook NotebookApply NotebookAutoSave NotebookClose NotebookConvertSettings NotebookCreate NotebookCreateReturnObject NotebookDefault NotebookDelete NotebookDirectory NotebookDynamicExpression NotebookEvaluate NotebookEventActions NotebookFileName NotebookFind NotebookFindReturnObject NotebookGet NotebookGetLayoutInformationPacket NotebookGetMisspellingsPacket NotebookInformation NotebookInterfaceObject NotebookLocate NotebookObject NotebookOpen NotebookOpenReturnObject NotebookPath NotebookPrint NotebookPut NotebookPutReturnObject NotebookRead NotebookResetGeneratedCells Notebooks NotebookSave NotebookSaveAs NotebookSelection NotebookSetupLayoutInformationPacket NotebooksMenu NotebookWrite NotElement NotEqualTilde NotExists NotGreater NotGreaterEqual NotGreaterFullEqual NotGreaterGreater NotGreaterLess NotGreaterSlantEqual NotGreaterTilde NotHumpDownHump NotHumpEqual NotLeftTriangle NotLeftTriangleBar NotLeftTriangleEqual NotLess NotLessEqual NotLessFullEqual NotLessGreater NotLessLess NotLessSlantEqual NotLessTilde NotNestedGreaterGreater NotNestedLessLess NotPrecedes NotPrecedesEqual NotPrecedesSlantEqual NotPrecedesTilde NotReverseElement NotRightTriangle NotRightTriangleBar NotRightTriangleEqual NotSquareSubset NotSquareSubsetEqual NotSquareSuperset NotSquareSupersetEqual NotSubset NotSubsetEqual NotSucceeds NotSucceedsEqual NotSucceedsSlantEqual NotSucceedsTilde NotSuperset NotSupersetEqual NotTilde NotTildeEqual NotTildeFullEqual NotTildeTilde NotVerticalBar NProbability NProduct NProductFactors NRoots NSolve NSum NSumTerms Null NullRecords NullSpace NullWords Number NumberFieldClassNumber NumberFieldDiscriminant NumberFieldFundamentalUnits NumberFieldIntegralBasis NumberFieldNormRepresentatives NumberFieldRegulator NumberFieldRootsOfUnity NumberFieldSignature NumberForm NumberFormat NumberMarks NumberMultiplier NumberPadding NumberPoint NumberQ NumberSeparator NumberSigns NumberString Numerator NumericFunction NumericQ NuttallWindow NValues NyquistGridLines NyquistPlot O ObservabilityGramian ObservabilityMatrix ObservableDecomposition ObservableModelQ OddQ Off Offset OLEData On ONanGroupON OneIdentity Opacity Open OpenAppend Opener OpenerBox OpenerBoxOptions OpenerView OpenFunctionInspectorPacket Opening OpenRead OpenSpecialOptions OpenTemporary OpenWrite Operate OperatingSystem OptimumFlowData Optional OptionInspectorSettings OptionQ Options OptionsPacket OptionsPattern OptionValue OptionValueBox OptionValueBoxOptions Or Orange Order OrderDistribution OrderedQ Ordering Orderless OrnsteinUhlenbeckProcess Orthogonalize Out Outer OutputAutoOverwrite OutputControllabilityMatrix OutputControllableModelQ OutputForm OutputFormData OutputGrouping OutputMathEditExpression OutputNamePacket OutputResponse OutputSizeLimit OutputStream Over OverBar OverDot Overflow OverHat Overlaps Overlay OverlayBox OverlayBoxOptions Overscript OverscriptBox OverscriptBoxOptions OverTilde OverVector OwenT OwnValues PackingMethod PaddedForm Padding PadeApproximant PadLeft PadRight PageBreakAbove PageBreakBelow PageBreakWithin PageFooterLines PageFooters PageHeaderLines PageHeaders PageHeight PageRankCentrality PageWidth PairedBarChart PairedHistogram PairedSmoothHistogram PairedTTest PairedZTest PaletteNotebook PalettePath Pane PaneBox PaneBoxOptions Panel PanelBox PanelBoxOptions Paneled PaneSelector PaneSelectorBox PaneSelectorBoxOptions PaperWidth ParabolicCylinderD ParagraphIndent ParagraphSpacing ParallelArray ParallelCombine ParallelDo ParallelEvaluate Parallelization Parallelize ParallelMap ParallelNeeds ParallelProduct ParallelSubmit ParallelSum ParallelTable ParallelTry Parameter ParameterEstimator ParameterMixtureDistribution ParameterVariables ParametricFunction ParametricNDSolve ParametricNDSolveValue ParametricPlot ParametricPlot3D ParentConnect ParentDirectory ParentForm Parenthesize ParentList ParetoDistribution Part PartialCorrelationFunction PartialD ParticleData Partition PartitionsP PartitionsQ ParzenWindow PascalDistribution PassEventsDown PassEventsUp Paste PasteBoxFormInlineCells PasteButton Path PathGraph PathGraphQ Pattern PatternSequence PatternTest PauliMatrix PaulWavelet Pause PausedTime PDF PearsonChiSquareTest PearsonCorrelationTest PearsonDistribution PerformanceGoal PeriodicInterpolation Periodogram PeriodogramArray PermutationCycles PermutationCyclesQ PermutationGroup PermutationLength PermutationList PermutationListQ PermutationMax PermutationMin PermutationOrder PermutationPower PermutationProduct PermutationReplace Permutations PermutationSupport Permute PeronaMalikFilter Perpendicular PERTDistribution PetersenGraph PhaseMargins Pi Pick PIDData PIDDerivativeFilter PIDFeedforward PIDTune Piecewise PiecewiseExpand PieChart PieChart3D PillaiTrace PillaiTraceTest Pink Pivoting PixelConstrained PixelValue PixelValuePositions Placed Placeholder PlaceholderReplace Plain PlanarGraphQ Play PlayRange Plot Plot3D Plot3Matrix PlotDivision PlotJoined PlotLabel PlotLayout PlotLegends PlotMarkers PlotPoints PlotRange PlotRangeClipping PlotRangePadding PlotRegion PlotStyle Plus PlusMinus Pochhammer PodStates PodWidth Point Point3DBox PointBox PointFigureChart PointForm PointLegend PointSize PoissonConsulDistribution PoissonDistribution PoissonProcess PoissonWindow PolarAxes PolarAxesOrigin PolarGridLines PolarPlot PolarTicks PoleZeroMarkers PolyaAeppliDistribution PolyGamma Polygon Polygon3DBox Polygon3DBoxOptions PolygonBox PolygonBoxOptions PolygonHoleScale PolygonIntersections PolygonScale PolyhedronData PolyLog PolynomialExtendedGCD PolynomialForm PolynomialGCD PolynomialLCM PolynomialMod PolynomialQ PolynomialQuotient PolynomialQuotientRemainder PolynomialReduce PolynomialRemainder Polynomials PopupMenu PopupMenuBox PopupMenuBoxOptions PopupView PopupWindow Position Positive PositiveDefiniteMatrixQ PossibleZeroQ Postfix PostScript Power PowerDistribution PowerExpand PowerMod PowerModList PowerSpectralDensity PowersRepresentations PowerSymmetricPolynomial Precedence PrecedenceForm Precedes PrecedesEqual PrecedesSlantEqual PrecedesTilde Precision PrecisionGoal PreDecrement PredictionRoot PreemptProtect PreferencesPath Prefix PreIncrement Prepend PrependTo PreserveImageOptions Previous PriceGraphDistribution PrimaryPlaceholder Prime PrimeNu PrimeOmega PrimePi PrimePowerQ PrimeQ Primes PrimeZetaP PrimitiveRoot PrincipalComponents PrincipalValue Print PrintAction PrintForm PrintingCopies PrintingOptions PrintingPageRange PrintingStartingPageNumber PrintingStyleEnvironment PrintPrecision PrintTemporary Prism PrismBox PrismBoxOptions PrivateCellOptions PrivateEvaluationOptions PrivateFontOptions PrivateFrontEndOptions PrivateNotebookOptions PrivatePaths Probability ProbabilityDistribution ProbabilityPlot ProbabilityPr ProbabilityScalePlot ProbitModelFit ProcessEstimator ProcessParameterAssumptions ProcessParameterQ ProcessStateDomain ProcessTimeDomain Product ProductDistribution ProductLog ProgressIndicator ProgressIndicatorBox ProgressIndicatorBoxOptions Projection Prolog PromptForm Properties Property PropertyList PropertyValue Proportion Proportional Protect Protected ProteinData Pruning PseudoInverse Purple Put PutAppend Pyramid PyramidBox PyramidBoxOptions QBinomial QFactorial QGamma QHypergeometricPFQ QPochhammer QPolyGamma QRDecomposition QuadraticIrrationalQ Quantile QuantilePlot Quantity QuantityForm QuantityMagnitude QuantityQ QuantityUnit Quartics QuartileDeviation Quartiles QuartileSkewness QueueingNetworkProcess QueueingProcess QueueProperties Quiet Quit Quotient QuotientRemainder RadialityCentrality RadicalBox RadicalBoxOptions RadioButton RadioButtonBar RadioButtonBox RadioButtonBoxOptions Radon RamanujanTau RamanujanTauL RamanujanTauTheta RamanujanTauZ Random RandomChoice RandomComplex RandomFunction RandomGraph RandomImage RandomInteger RandomPermutation RandomPrime RandomReal RandomSample RandomSeed RandomVariate RandomWalkProcess Range RangeFilter RangeSpecification RankedMax RankedMin Raster Raster3D Raster3DBox Raster3DBoxOptions RasterArray RasterBox RasterBoxOptions Rasterize RasterSize Rational RationalFunctions Rationalize Rationals Ratios Raw RawArray RawBoxes RawData RawMedium RayleighDistribution Re Read ReadList ReadProtected Real RealBlockDiagonalForm RealDigits RealExponent Reals Reap Record RecordLists RecordSeparators Rectangle RectangleBox RectangleBoxOptions RectangleChart RectangleChart3D RecurrenceFilter RecurrenceTable RecurringDigitsForm Red Reduce RefBox ReferenceLineStyle ReferenceMarkers ReferenceMarkerStyle Refine ReflectionMatrix ReflectionTransform Refresh RefreshRate RegionBinarize RegionFunction RegionPlot RegionPlot3D RegularExpression Regularization Reinstall Release ReleaseHold ReliabilityDistribution ReliefImage ReliefPlot Remove RemoveAlphaChannel RemoveAsynchronousTask Removed RemoveInputStreamMethod RemoveOutputStreamMethod RemoveProperty RemoveScheduledTask RenameDirectory RenameFile RenderAll RenderingOptions RenewalProcess RenkoChart Repeated RepeatedNull RepeatedString Replace ReplaceAll ReplaceHeldPart ReplaceImageValue ReplaceList ReplacePart ReplacePixelValue ReplaceRepeated Resampling Rescale RescalingTransform ResetDirectory ResetMenusPacket ResetScheduledTask Residue Resolve Rest Resultant ResumePacket Return ReturnExpressionPacket ReturnInputFormPacket ReturnPacket ReturnTextPacket Reverse ReverseBiorthogonalSplineWavelet ReverseElement ReverseEquilibrium ReverseGraph ReverseUpEquilibrium RevolutionAxis RevolutionPlot3D RGBColor RiccatiSolve RiceDistribution RidgeFilter RiemannR RiemannSiegelTheta RiemannSiegelZ Riffle Right RightArrow RightArrowBar RightArrowLeftArrow RightCosetRepresentative RightDownTeeVector RightDownVector RightDownVectorBar RightTee RightTeeArrow RightTeeVector RightTriangle RightTriangleBar RightTriangleEqual RightUpDownVector RightUpTeeVector RightUpVector RightUpVectorBar RightVector RightVectorBar RiskAchievementImportance RiskReductionImportance RogersTanimotoDissimilarity Root RootApproximant RootIntervals RootLocusPlot RootMeanSquare RootOfUnityQ RootReduce Roots RootSum Rotate RotateLabel RotateLeft RotateRight RotationAction RotationBox RotationBoxOptions RotationMatrix RotationTransform Round RoundImplies RoundingRadius Row RowAlignments RowBackgrounds RowBox RowHeights RowLines RowMinHeight RowReduce RowsEqual RowSpacings RSolve RudvalisGroupRu Rule RuleCondition RuleDelayed RuleForm RulerUnits Run RunScheduledTask RunThrough RuntimeAttributes RuntimeOptions RussellRaoDissimilarity SameQ SameTest SampleDepth SampledSoundFunction SampledSoundList SampleRate SamplingPeriod SARIMAProcess SARMAProcess SatisfiabilityCount SatisfiabilityInstances SatisfiableQ Saturday Save Saveable SaveAutoDelete SaveDefinitions SawtoothWave Scale Scaled ScaleDivisions ScaledMousePosition ScaleOrigin ScalePadding ScaleRanges ScaleRangeStyle ScalingFunctions ScalingMatrix ScalingTransform Scan ScheduledTaskActiveQ ScheduledTaskData ScheduledTaskObject ScheduledTasks SchurDecomposition ScientificForm ScreenRectangle ScreenStyleEnvironment ScriptBaselineShifts ScriptLevel ScriptMinSize ScriptRules ScriptSizeMultipliers Scrollbars ScrollingOptions ScrollPosition Sec Sech SechDistribution SectionGrouping SectorChart SectorChart3D SectorOrigin SectorSpacing SeedRandom Select Selectable SelectComponents SelectedCells SelectedNotebook Selection SelectionAnimate SelectionCell SelectionCellCreateCell SelectionCellDefaultStyle SelectionCellParentStyle SelectionCreateCell SelectionDebuggerTag SelectionDuplicateCell SelectionEvaluate SelectionEvaluateCreateCell SelectionMove SelectionPlaceholder SelectionSetStyle SelectWithContents SelfLoops SelfLoopStyle SemialgebraicComponentInstances SendMail Sequence SequenceAlignment SequenceForm SequenceHold SequenceLimit Series SeriesCoefficient SeriesData SessionTime Set SetAccuracy SetAlphaChannel SetAttributes Setbacks SetBoxFormNamesPacket SetDelayed SetDirectory SetEnvironment SetEvaluationNotebook SetFileDate SetFileLoadingContext SetNotebookStatusLine SetOptions SetOptionsPacket SetPrecision SetProperty SetSelectedNotebook SetSharedFunction SetSharedVariable SetSpeechParametersPacket SetStreamPosition SetSystemOptions Setter SetterBar SetterBox SetterBoxOptions Setting SetValue Shading Shallow ShannonWavelet ShapiroWilkTest Share Sharpen ShearingMatrix ShearingTransform ShenCastanMatrix Short ShortDownArrow Shortest ShortestMatch ShortestPathFunction ShortLeftArrow ShortRightArrow ShortUpArrow Show ShowAutoStyles ShowCellBracket ShowCellLabel ShowCellTags ShowClosedCellArea ShowContents ShowControls ShowCursorTracker ShowGroupOpenCloseIcon ShowGroupOpener ShowInvisibleCharacters ShowPageBreaks ShowPredictiveInterface ShowSelection ShowShortBoxForm ShowSpecialCharacters ShowStringCharacters ShowSyntaxStyles ShrinkingDelay ShrinkWrapBoundingBox SiegelTheta SiegelTukeyTest Sign Signature SignedRankTest SignificanceLevel SignPadding SignTest SimilarityRules SimpleGraph SimpleGraphQ Simplify Sin Sinc SinghMaddalaDistribution SingleEvaluation SingleLetterItalics SingleLetterStyle SingularValueDecomposition SingularValueList SingularValuePlot SingularValues Sinh SinhIntegral SinIntegral SixJSymbol Skeleton SkeletonTransform SkellamDistribution Skewness SkewNormalDistribution Skip SliceDistribution Slider Slider2D Slider2DBox Slider2DBoxOptions SliderBox SliderBoxOptions SlideView Slot SlotSequence Small SmallCircle Smaller SmithDelayCompensator SmithWatermanSimilarity SmoothDensityHistogram SmoothHistogram SmoothHistogram3D SmoothKernelDistribution SocialMediaData Socket SokalSneathDissimilarity Solve SolveAlways SolveDelayed Sort SortBy Sound SoundAndGraphics SoundNote SoundVolume Sow Space SpaceForm Spacer Spacings Span SpanAdjustments SpanCharacterRounding SpanFromAbove SpanFromBoth SpanFromLeft SpanLineThickness SpanMaxSize SpanMinSize SpanningCharacters SpanSymmetric SparseArray SpatialGraphDistribution Speak SpeakTextPacket SpearmanRankTest SpearmanRho Spectrogram SpectrogramArray Specularity SpellingCorrection SpellingDictionaries SpellingDictionariesPath SpellingOptions SpellingSuggestionsPacket Sphere SphereBox SphericalBesselJ SphericalBesselY SphericalHankelH1 SphericalHankelH2 SphericalHarmonicY SphericalPlot3D SphericalRegion SpheroidalEigenvalue SpheroidalJoiningFactor SpheroidalPS SpheroidalPSPrime SpheroidalQS SpheroidalQSPrime SpheroidalRadialFactor SpheroidalS1 SpheroidalS1Prime SpheroidalS2 SpheroidalS2Prime Splice SplicedDistribution SplineClosed SplineDegree SplineKnots SplineWeights Split SplitBy SpokenString Sqrt SqrtBox SqrtBoxOptions Square SquaredEuclideanDistance SquareFreeQ SquareIntersection SquaresR SquareSubset SquareSubsetEqual SquareSuperset SquareSupersetEqual SquareUnion SquareWave StabilityMargins StabilityMarginsStyle StableDistribution Stack StackBegin StackComplete StackInhibit StandardDeviation StandardDeviationFilter StandardForm Standardize StandbyDistribution Star StarGraph StartAsynchronousTask StartingStepSize StartOfLine StartOfString StartScheduledTask StartupSound StateDimensions StateFeedbackGains StateOutputEstimator StateResponse StateSpaceModel StateSpaceRealization StateSpaceTransform StationaryDistribution StationaryWaveletPacketTransform StationaryWaveletTransform StatusArea StatusCentrality StepMonitor StieltjesGamma StirlingS1 StirlingS2 StopAsynchronousTask StopScheduledTask StrataVariables StratonovichProcess StreamColorFunction StreamColorFunctionScaling StreamDensityPlot StreamPlot StreamPoints StreamPosition Streams StreamScale StreamStyle String StringBreak StringByteCount StringCases StringCount StringDrop StringExpression StringForm StringFormat StringFreeQ StringInsert StringJoin StringLength StringMatchQ StringPosition StringQ StringReplace StringReplaceList StringReplacePart StringReverse StringRotateLeft StringRotateRight StringSkeleton StringSplit StringTake StringToStream StringTrim StripBoxes StripOnInput StripWrapperBoxes StrokeForm StructuralImportance StructuredArray StructuredSelection StruveH StruveL Stub StudentTDistribution Style StyleBox StyleBoxAutoDelete StyleBoxOptions StyleData StyleDefinitions StyleForm StyleKeyMapping StyleMenuListing StyleNameDialogSettings StyleNames StylePrint StyleSheetPath Subfactorial Subgraph SubMinus SubPlus SubresultantPolynomialRemainders SubresultantPolynomials Subresultants Subscript SubscriptBox SubscriptBoxOptions Subscripted Subset SubsetEqual Subsets SubStar Subsuperscript SubsuperscriptBox SubsuperscriptBoxOptions Subtract SubtractFrom SubValues Succeeds SucceedsEqual SucceedsSlantEqual SucceedsTilde SuchThat Sum SumConvergence Sunday SuperDagger SuperMinus SuperPlus Superscript SuperscriptBox SuperscriptBoxOptions Superset SupersetEqual SuperStar Surd SurdForm SurfaceColor SurfaceGraphics SurvivalDistribution SurvivalFunction SurvivalModel SurvivalModelFit SuspendPacket SuzukiDistribution SuzukiGroupSuz SwatchLegend Switch Symbol SymbolName SymletWavelet Symmetric SymmetricGroup SymmetricMatrixQ SymmetricPolynomial SymmetricReduction Symmetrize SymmetrizedArray SymmetrizedArrayRules SymmetrizedDependentComponents SymmetrizedIndependentComponents SymmetrizedReplacePart SynchronousInitialization SynchronousUpdating Syntax SyntaxForm SyntaxInformation SyntaxLength SyntaxPacket SyntaxQ SystemDialogInput SystemException SystemHelpPath SystemInformation SystemInformationData SystemOpen SystemOptions SystemsModelDelay SystemsModelDelayApproximate SystemsModelDelete SystemsModelDimensions SystemsModelExtract SystemsModelFeedbackConnect SystemsModelLabels SystemsModelOrder SystemsModelParallelConnect SystemsModelSeriesConnect SystemsModelStateFeedbackConnect SystemStub Tab TabFilling Table TableAlignments TableDepth TableDirections TableForm TableHeadings TableSpacing TableView TableViewBox TabSpacings TabView TabViewBox TabViewBoxOptions TagBox TagBoxNote TagBoxOptions TaggingRules TagSet TagSetDelayed TagStyle TagUnset Take TakeWhile Tally Tan Tanh TargetFunctions TargetUnits TautologyQ TelegraphProcess TemplateBox TemplateBoxOptions TemplateSlotSequence TemporalData Temporary TemporaryVariable TensorContract TensorDimensions TensorExpand TensorProduct TensorQ TensorRank TensorReduce TensorSymmetry TensorTranspose TensorWedge Tetrahedron TetrahedronBox TetrahedronBoxOptions TeXForm TeXSave Text Text3DBox Text3DBoxOptions TextAlignment TextBand TextBoundingBox TextBox TextCell TextClipboardType TextData TextForm TextJustification TextLine TextPacket TextParagraph TextRecognize TextRendering TextStyle Texture TextureCoordinateFunction TextureCoordinateScaling Therefore ThermometerGauge Thick Thickness Thin Thinning ThisLink ThompsonGroupTh Thread ThreeJSymbol Threshold Through Throw Thumbnail Thursday Ticks TicksStyle Tilde TildeEqual TildeFullEqual TildeTilde TimeConstrained TimeConstraint Times TimesBy TimeSeriesForecast TimeSeriesInvertibility TimeUsed TimeValue TimeZone Timing Tiny TitleGrouping TitsGroupT ToBoxes ToCharacterCode ToColor ToContinuousTimeModel ToDate ToDiscreteTimeModel ToeplitzMatrix ToExpression ToFileName Together Toggle ToggleFalse Toggler TogglerBar TogglerBox TogglerBoxOptions ToHeldExpression ToInvertibleTimeSeries TokenWords Tolerance ToLowerCase ToNumberField TooBig Tooltip TooltipBox TooltipBoxOptions TooltipDelay TooltipStyle Top TopHatTransform TopologicalSort ToRadicals ToRules ToString Total TotalHeight TotalVariationFilter TotalWidth TouchscreenAutoZoom TouchscreenControlPlacement ToUpperCase Tr Trace TraceAbove TraceAction TraceBackward TraceDepth TraceDialog TraceForward TraceInternal TraceLevel TraceOff TraceOn TraceOriginal TracePrint TraceScan TrackedSymbols TradingChart TraditionalForm TraditionalFunctionNotation TraditionalNotation TraditionalOrder TransferFunctionCancel TransferFunctionExpand TransferFunctionFactor TransferFunctionModel TransferFunctionPoles TransferFunctionTransform TransferFunctionZeros TransformationFunction TransformationFunctions TransformationMatrix TransformedDistribution TransformedField Translate TranslationTransform TransparentColor Transpose TreeForm TreeGraph TreeGraphQ TreePlot TrendStyle TriangleWave TriangularDistribution Trig TrigExpand TrigFactor TrigFactorList Trigger TrigReduce TrigToExp TrimmedMean True TrueQ TruncatedDistribution TsallisQExponentialDistribution TsallisQGaussianDistribution TTest Tube TubeBezierCurveBox TubeBezierCurveBoxOptions TubeBox TubeBSplineCurveBox TubeBSplineCurveBoxOptions Tuesday TukeyLambdaDistribution TukeyWindow Tuples TuranGraph TuringMachine Transparent UnateQ Uncompress Undefined UnderBar Underflow Underlined Underoverscript UnderoverscriptBox UnderoverscriptBoxOptions Underscript UnderscriptBox UnderscriptBoxOptions UndirectedEdge UndirectedGraph UndirectedGraphQ UndocumentedTestFEParserPacket UndocumentedTestGetSelectionPacket Unequal Unevaluated UniformDistribution UniformGraphDistribution UniformSumDistribution Uninstall Union UnionPlus Unique UnitBox UnitConvert UnitDimensions Unitize UnitRootTest UnitSimplify UnitStep UnitTriangle UnitVector Unprotect UnsameQ UnsavedVariables Unset UnsetShared UntrackedVariables Up UpArrow UpArrowBar UpArrowDownArrow Update UpdateDynamicObjects UpdateDynamicObjectsSynchronous UpdateInterval UpDownArrow UpEquilibrium UpperCaseQ UpperLeftArrow UpperRightArrow UpperTriangularize Upsample UpSet UpSetDelayed UpTee UpTeeArrow UpValues URL URLFetch URLFetchAsynchronous URLSave URLSaveAsynchronous UseGraphicsRange Using UsingFrontEnd V2Get ValidationLength Value ValueBox ValueBoxOptions ValueForm ValueQ ValuesData Variables Variance VarianceEquivalenceTest VarianceEstimatorFunction VarianceGammaDistribution VarianceTest VectorAngle VectorColorFunction VectorColorFunctionScaling VectorDensityPlot VectorGlyphData VectorPlot VectorPlot3D VectorPoints VectorQ Vectors VectorScale VectorStyle Vee Verbatim Verbose VerboseConvertToPostScriptPacket VerifyConvergence VerifySolutions VerifyTestAssumptions Version VersionNumber VertexAdd VertexCapacity VertexColors VertexComponent VertexConnectivity VertexCoordinateRules VertexCoordinates VertexCorrelationSimilarity VertexCosineSimilarity VertexCount VertexCoverQ VertexDataCoordinates VertexDegree VertexDelete VertexDiceSimilarity VertexEccentricity VertexInComponent VertexInDegree VertexIndex VertexJaccardSimilarity VertexLabeling VertexLabels VertexLabelStyle VertexList VertexNormals VertexOutComponent VertexOutDegree VertexQ VertexRenderingFunction VertexReplace VertexShape VertexShapeFunction VertexSize VertexStyle VertexTextureCoordinates VertexWeight Vertical VerticalBar VerticalForm VerticalGauge VerticalSeparator VerticalSlider VerticalTilde ViewAngle ViewCenter ViewMatrix ViewPoint ViewPointSelectorSettings ViewPort ViewRange ViewVector ViewVertical VirtualGroupData Visible VisibleCell VoigtDistribution VonMisesDistribution WaitAll WaitAsynchronousTask WaitNext WaitUntil WakebyDistribution WalleniusHypergeometricDistribution WaringYuleDistribution WatershedComponents WatsonUSquareTest WattsStrogatzGraphDistribution WaveletBestBasis WaveletFilterCoefficients WaveletImagePlot WaveletListPlot WaveletMapIndexed WaveletMatrixPlot WaveletPhi WaveletPsi WaveletScale WaveletScalogram WaveletThreshold WeaklyConnectedComponents WeaklyConnectedGraphQ WeakStationarity WeatherData WeberE Wedge Wednesday WeibullDistribution WeierstrassHalfPeriods WeierstrassInvariants WeierstrassP WeierstrassPPrime WeierstrassSigma WeierstrassZeta WeightedAdjacencyGraph WeightedAdjacencyMatrix WeightedData WeightedGraphQ Weights WelchWindow WheelGraph WhenEvent Which While White Whitespace WhitespaceCharacter WhittakerM WhittakerW WienerFilter WienerProcess WignerD WignerSemicircleDistribution WilksW WilksWTest WindowClickSelect WindowElements WindowFloating WindowFrame WindowFrameElements WindowMargins WindowMovable WindowOpacity WindowSelected WindowSize WindowStatusArea WindowTitle WindowToolbars WindowWidth With WolframAlpha WolframAlphaDate WolframAlphaQuantity WolframAlphaResult Word WordBoundary WordCharacter WordData WordSearch WordSeparators WorkingPrecision Write WriteString Wronskian XMLElement XMLObject Xnor Xor Yellow YuleDissimilarity ZernikeR ZeroSymmetric ZeroTest ZeroWidthTimes Zeta ZetaZero ZipfDistribution ZTest ZTransform $Aborted $ActivationGroupID $ActivationKey $ActivationUserRegistered $AddOnsDirectory $AssertFunction $Assumptions $AsynchronousTask $BaseDirectory $BatchInput $BatchOutput $BoxForms $ByteOrdering $Canceled $CharacterEncoding $CharacterEncodings $CommandLine $CompilationTarget $ConditionHold $ConfiguredKernels $Context $ContextPath $ControlActiveSetting $CreationDate $CurrentLink $DateStringFormat $DefaultFont $DefaultFrontEnd $DefaultImagingDevice $DefaultPath $Display $DisplayFunction $DistributedContexts $DynamicEvaluation $Echo $Epilog $ExportFormats $Failed $FinancialDataSource $FormatType $FrontEnd $FrontEndSession $GeoLocation $HistoryLength $HomeDirectory $HTTPCookies $IgnoreEOF $ImagingDevices $ImportFormats $InitialDirectory $Input $InputFileName $InputStreamMethods $Inspector $InstallationDate $InstallationDirectory $InterfaceEnvironment $IterationLimit $KernelCount $KernelID $Language $LaunchDirectory $LibraryPath $LicenseExpirationDate $LicenseID $LicenseProcesses $LicenseServer $LicenseSubprocesses $LicenseType $Line $Linked $LinkSupported $LoadedFiles $MachineAddresses $MachineDomain $MachineDomains $MachineEpsilon $MachineID $MachineName $MachinePrecision $MachineType $MaxExtraPrecision $MaxLicenseProcesses $MaxLicenseSubprocesses $MaxMachineNumber $MaxNumber $MaxPiecewiseCases $MaxPrecision $MaxRootDegree $MessageGroups $MessageList $MessagePrePrint $Messages $MinMachineNumber $MinNumber $MinorReleaseNumber $MinPrecision $ModuleNumber $NetworkLicense $NewMessage $NewSymbol $Notebooks $NumberMarks $Off $OperatingSystem $Output $OutputForms $OutputSizeLimit $OutputStreamMethods $Packages $ParentLink $ParentProcessID $PasswordFile $PatchLevelID $Path $PathnameSeparator $PerformanceGoal $PipeSupported $Post $Pre $PreferencesDirectory $PrePrint $PreRead $PrintForms $PrintLiteral $ProcessID $ProcessorCount $ProcessorType $ProductInformation $ProgramName $RandomState $RecursionLimit $ReleaseNumber $RootDirectory $ScheduledTask $ScriptCommandLine $SessionID $SetParentLink $SharedFunctions $SharedVariables $SoundDisplay $SoundDisplayFunction $SuppressInputFormHeads $SynchronousEvaluation $SyntaxHandler $System $SystemCharacterEncoding $SystemID $SystemWordLength $TemporaryDirectory $TemporaryPrefix $TextStyle $TimedOut $TimeUnit $TimeZone $TopDirectory $TraceOff $TraceOn $TracePattern $TracePostAction $TracePreAction $Urgent $UserAddOnsDirectory $UserBaseDirectory $UserDocumentsDirectory $UserName $Version $VersionNumber", -c:[{cN:"comment",b:/\(\*/,e:/\*\)/},e.ASM,e.QSM,e.CNM,{cN:"list",b:/\{/,e:/\}/,i:/:/}]}});hljs.registerLanguage("python",function(e){var r={cN:"prompt",b:/^(>>>|\.\.\.) /},b={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[r],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]},a={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},l={cN:"params",b:/\(/,e:/\)/,c:["self",r,a,b]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[r,a,b,e.HCM,{v:[{cN:"function",bK:"def",r:10},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,l]},{cN:"decorator",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("java",function(e){var a=e.UIR+"(<"+e.UIR+">)?",t="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private",c="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",r={cN:"number",b:c,r:0};return{aliases:["jsp"],k:t,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+a+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:t,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},r,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"title",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,e.NM,s,a,t]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{cN:"operator",bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke",e:/;/,eW:!0,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes c cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle d data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration e each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract f failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function g general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http i id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists k keep keep_duplicates key keys kill l language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim m main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex n name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding p package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime t table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[t.inherit(t.QSM,{b:'((u8?|U)|L)?"'}),{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},{b:t.CNR}]},i={cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line pragma ifdef ifndef",c:[{b:/\\\n/,r:0},{bK:"include",e:"$",c:[r,{cN:"string",b:"<",e:">",i:"\\n"}]},r,s,t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf",literal:"true false nullptr NULL"};return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"",k:c,c:["self",e]},{b:t.IR+"::",k:c},{bK:"new throw return else",r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s]},t.CLCM,t.CBCM,i]}]}});hljs.registerLanguage("matlab",function(e){var a=[e.CNM,{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]}],s={r:0,c:[{cN:"operator",b:/'['\.]*/}]};return{k:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson"},i:'(//|"|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function",e:"$",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)"},{cN:"params",b:"\\[",e:"\\]"}]},{b:/[a-zA-Z_][a-zA-Z_0-9]*'['\.]*/,rB:!0,r:0,c:[{b:/[a-zA-Z_][a-zA-Z_0-9]*/,r:0},s.c[0]]},{cN:"matrix",b:"\\[",e:"\\]",c:a,r:0,starts:s},{cN:"cell",b:"\\{",e:/}/,c:a,r:0,starts:s},{b:/\)/,r:0,starts:s},e.C("^\\s*\\%\\{\\s*$","^\\s*\\%\\}\\s*$"),e.C("\\%","$")].concat(a)}});hljs.registerLanguage("elm",function(e){var c=[e.C("--","$"),e.C("{-","-}",{c:["self"]})],i={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n={cN:"container",b:"\\(",e:"\\)",i:'"',c:[{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"}].concat(c)},t={cN:"container",b:"{",e:"}",c:n.c};return{k:"let in if then else case of where module import exposing type alias as infix infixl infixr port",c:[{cN:"module",b:"\\bmodule\\b",e:"where",k:"module where",c:[n].concat(c),i:"\\W\\.|;"},{cN:"import",b:"\\bimport\\b",e:"$",k:"import|0 as exposing",c:[n].concat(c),i:"\\W\\.|;"},{cN:"typedef",b:"\\btype\\b",e:"$",k:"type alias",c:[i,n,t].concat(c)},{cN:"infix",bK:"infix infixl infixr",e:"$",c:[e.CNM].concat(c)},{cN:"foreign",b:"\\bport\\b",e:"$",k:"port",c:c},e.QSM,e.CNM,i,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),{b:"->|<-"}].concat(c)}});hljs.registerLanguage("php",function(e){var c={cN:"variable",b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},a={cN:"preprocessor",b:/<\?(php)?|\?>/},i={cN:"string",c:[e.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},t={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.CLCM,e.HCM,e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"},a]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},a,c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,i,t]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},i,t]}});hljs.registerLanguage("vbnet",function(e){return{aliases:["vb"],cI:!0,k:{keyword:"addhandler addressof alias and andalso aggregate ansi as assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into is isfalse isnot istrue join key let lib like loop me mid mod module mustinherit mustoverride mybase myclass namespace narrowing new next not notinheritable notoverridable of off on operator option optional or order orelse overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim rem removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly xor",built_in:"boolean byte cbool cbyte cchar cdate cdec cdbl char cint clng cobj csbyte cshort csng cstr ctype date decimal directcast double gettype getxmlnamespace iif integer long object sbyte short single string trycast typeof uinteger ulong ushort",literal:"true false nothing"},i:"//|{|}|endif|gosub|variant|wend",c:[e.inherit(e.QSM,{c:[{b:'""'}]}),e.C("'","$",{rB:!0,c:[{cN:"xmlDocTag",b:"'''|",c:[e.PWM]},{cN:"xmlDocTag",b:"",c:[e.PWM]}]}),e.CNM,{cN:"preprocessor",b:"#",e:"$",k:"if else elseif end region externalsource"}]}});hljs.registerLanguage("dart",function(e){var t={cN:"subst",b:"\\$\\{",e:"}",k:"true false null this is new super"},r={cN:"string",v:[{b:"r'''",e:"'''"},{b:'r"""',e:'"""'},{b:"r'",e:"'",i:"\\n"},{b:'r"',e:'"',i:"\\n"},{b:"'''",e:"'''",c:[e.BE,t]},{b:'"""',e:'"""',c:[e.BE,t]},{b:"'",e:"'",i:"\\n",c:[e.BE,t]},{b:'"',e:'"',i:"\\n",c:[e.BE,t]}]};t.c=[e.CNM,r];var n={keyword:"assert break case catch class const continue default do else enum extends false final finally for if in is new null rethrow return super switch this throw true try var void while with",literal:"abstract as dynamic export external factory get implements import library operator part set static typedef",built_in:"print Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double int num document window querySelector querySelectorAll Element ElementList"};return{k:n,c:[r,e.C("/\\*\\*","\\*/",{sL:"markdown"}),e.C("///","$",{sL:"markdown"}),e.CLCM,e.CBCM,{cN:"class",bK:"class interface",e:"{",eE:!0,c:[{bK:"extends implements"},e.UTM]},e.CNM,{cN:"annotation",b:"@[A-Za-z]+"},{b:"=>"}]}});hljs.registerLanguage("vbscript",function(e){return{aliases:["vbs"],cI:!0,k:{keyword:"call class const dim do loop erase execute executeglobal exit for each next function if then else on error option explicit new private property let get public randomize redim rem select case set stop sub while wend with end to elseif is or xor and not class_initialize class_terminate default preserve in me byval byref step resume goto",built_in:"lcase month vartype instrrev ubound setlocale getobject rgb getref string weekdayname rnd dateadd monthname now day minute isarray cbool round formatcurrency conversions csng timevalue second year space abs clng timeserial fixs len asc isempty maths dateserial atn timer isobject filter weekday datevalue ccur isdate instr datediff formatdatetime replace isnull right sgn array snumeric log cdbl hex chr lbound msgbox ucase getlocale cos cdate cbyte rtrim join hour oct typename trim strcomp int createobject loadpicture tan formatnumber mid scriptenginebuildversion scriptengine split scriptengineminorversion cint sin datepart ltrim sqr scriptenginemajorversion time derived eval date formatpercent exp inputbox left ascw chrw regexp server response request cstr err",literal:"true false null nothing empty"},i:"//",c:[e.inherit(e.QSM,{c:[{b:'""'}]}),e.C(/'/,/$/,{r:0}),e.CNM]}});hljs.registerLanguage("fortran",function(e){var t={cN:"params",b:"\\(",e:"\\)"},n={constant:".False. .True.",type:"integer real character complex logical dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data",keyword:"kind do while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure",built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_ofacosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image"};return{cI:!0,aliases:["f90","f95"],k:n,c:[e.inherit(e.ASM,{cN:"string",r:0}),e.inherit(e.QSM,{cN:"string",r:0}),{cN:"function",bK:"subroutine function program",i:"[${=\\n]",c:[e.UTM,t]},e.C("!","$",{r:0}),{cN:"number",b:"(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?",r:0}]}});hljs.registerLanguage("scheme",function(e){var t="[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",r="(\\-|\\+)?\\d+([./]\\d+)?",i=r+"[+\\-]"+r+"i",a={built_in:"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"},n={cN:"shebang",b:"^#!",e:"$"},c={cN:"literal",b:"(#t|#f|#\\\\"+t+"|#\\\\.)"},l={cN:"number",v:[{b:r,r:0},{b:i,r:0},{b:"#b[0-1]+(/[0-1]+)?"},{b:"#o[0-7]+(/[0-7]+)?"},{b:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},s=e.QSM,o=[e.C(";","$",{r:0}),e.C("#\\|","\\|#")],u={b:t,r:0},p={cN:"variable",b:"'"+t},d={eW:!0,r:0},g={cN:"list",v:[{b:"\\(",e:"\\)"},{b:"\\[",e:"\\]"}],c:[{cN:"keyword",b:t,l:t,k:a},d]};return d.c=[c,l,s,u,p,g].concat(o),{i:/\S/,c:[n,l,s,p,g].concat(o)}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},t=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{cN:"property",b:"@"+n},{b:"`",e:"`",eB:!0,eE:!0,sL:"javascript"}];r.c=t;var s=e.inherit(e.TM,{b:n}),i="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(t)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:t.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+i,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:i,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{cN:"attribute",b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("haml",function(s){return{cI:!0,c:[{cN:"doctype",b:"^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$",r:10},s.C("^\\s*(!=#|=#|-#|/).*$",!1,{r:0}),{b:"^\\s*(-|=|!=)(?!#)",starts:{e:"\\n",sL:"ruby"}},{cN:"tag",b:"^\\s*%",c:[{cN:"title",b:"\\w+"},{cN:"value",b:"[#\\.][\\w-]+"},{b:"{\\s*",e:"\\s*}",eE:!0,c:[{b:":\\w+\\s*=>",e:",\\s+",rB:!0,eW:!0,c:[{cN:"symbol",b:":\\w+"},s.ASM,s.QSM,{b:"\\w+",r:0}]}]},{b:"\\(\\s*",e:"\\s*\\)",eE:!0,c:[{b:"\\w+\\s*=",e:"\\s+",rB:!0,eW:!0,c:[{cN:"attribute",b:"\\w+",r:0},s.ASM,s.QSM,{b:"\\w+",r:0}]}]}]},{cN:"bullet",b:"^\\s*[=~]\\s*",r:0},{b:"#{",starts:{e:"}",sL:"ruby"}}]}});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"pi",r:10,b:/^\s*['"]use (strict|asm)['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{bK:"import",e:"[;$]",k:"import from as",c:[e.ASM,e.QSM]},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]}],i:/#/}});hljs.registerLanguage("erlang",function(e){var r="[a-z'][a-zA-Z0-9_']*",c="("+r+":"+r+"|"+r+")",a={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"},n=e.C("%","$"),i={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},b={b:"fun\\s+"+r+"/\\d+"},d={b:c+"\\(",e:"\\)",rB:!0,r:0,c:[{cN:"function_name",b:c,r:0},{b:"\\(",e:"\\)",eW:!0,rE:!0,r:0}]},o={cN:"tuple",b:"{",e:"}",r:0},t={cN:"variable",b:"\\b_([A-Z][A-Za-z0-9_]*)?",r:0},l={cN:"variable",b:"[A-Z][a-zA-Z0-9_]*",r:0},f={b:"#"+e.UIR,r:0,rB:!0,c:[{cN:"record_name",b:"#"+e.UIR,r:0},{b:"{",e:"}",r:0}]},s={bK:"fun receive if try case",e:"end",k:a};s.c=[n,b,e.inherit(e.ASM,{cN:""}),s,d,e.QSM,i,o,t,l,f];var u=[n,b,s,d,e.QSM,i,o,t,l,f];d.c[1].c=u,o.c=u,f.c[1].c=u;var v={cN:"params",b:"\\(",e:"\\)",c:u};return{aliases:["erl"],k:a,i:"(",rB:!0,i:"\\(|#|//|/\\*|\\\\|:|;",c:[v,e.inherit(e.TM,{b:r})],starts:{e:";|\\.",k:a,c:u}},n,{cN:"pp",b:"^-",e:"\\.",r:0,eE:!0,rB:!0,l:"-"+e.IR,k:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec",c:[v]},i,e.QSM,f,t,l,o,{b:/\.$/}]}});hljs.registerLanguage("dns",function(d){return{aliases:["bind","zone"],k:{keyword:"IN A AAAA AFSDB APL CAA CDNSKEY CDS CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SRV SSHFP TA TKEY TLSA TSIG TXT"},c:[d.C(";","$"),{cN:"operator",bK:"$TTL $GENERATE $INCLUDE $ORIGIN"},{cN:"number",b:"((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))"},{cN:"number",b:"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"}]}});hljs.registerLanguage("ocaml",function(e){return{aliases:["ml"],k:{keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value",built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref",literal:"true false"},i:/\/\/|>>/,l:"[a-z_]\\w*!?",c:[{cN:"literal",b:"\\[(\\|\\|)?\\]|\\(\\)",r:0},e.C("\\(\\*","\\*\\)",{c:["self"]}),{cN:"symbol",b:"'[A-Za-z_](?!')[\\w']*"},{cN:"tag",b:"`[A-Z][\\w']*"},{cN:"type",b:"\\b[A-Z][\\w']*",r:0},{b:"[a-z_]\\w*'[\\w']*"},e.inherit(e.ASM,{cN:"char",r:0}),e.inherit(e.QSM,{i:null}),{cN:"number",b:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",r:0},{b:/[-=]>/}]}});hljs.registerLanguage("clojure",function(e){var t={built_in:"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},c=e.inherit(e.QSM,{i:null}),i=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={cN:"collection",b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"attribute",b:"[:]"+n},f={cN:"list",b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"keyword",b:n,starts:h},b=[f,c,m,p,i,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,c,m,p,i,u,l,s,d]}});hljs.registerLanguage("x86asm",function(s){return{cI:!0,l:"\\.?"+s.IR,k:{keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",literal:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l",pseudo:"db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times",preprocessor:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public ",built_in:"bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},c:[s.C(";","$",{r:0}),{cN:"number",v:[{b:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",r:0},{b:"\\$[0-9][0-9A-Fa-f]*",r:0},{b:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{b:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QSM,{cN:"string",v:[{b:"'",e:"[^\\\\]'"},{b:"`",e:"[^\\\\]`"},{b:"\\.[A-Za-z0-9]+"}],r:0},{cN:"label",v:[{b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{b:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],r:0},{cN:"argument",b:"%[0-9]+",r:0},{cN:"built_in",b:"%!S+",r:0}]}});hljs.registerLanguage("tcl",function(e){return{aliases:["tk"],k:"after append apply array auto_execok auto_import auto_load auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock close concat continue dde dict encoding eof error eval exec exit expr fblocked fconfigure fcopy file fileevent filename flush for foreach format gets glob global history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename return safe scan seek set socket source split string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update uplevel upvar variable vwait while",c:[e.C(";[ \\t]*#","$"),e.C("^[ \\t]*#","$"),{bK:"proc",e:"[\\{]",eE:!0,c:[{cN:"symbol",b:"[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"[ \\t\\n\\r]",eW:!0,eE:!0}]},{cN:"variable",eE:!0,v:[{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*\\(([a-zA-Z0-9_])*\\)",e:"[^a-zA-Z0-9_\\}\\$]"},{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"(\\))?[^a-zA-Z0-9_\\}\\$]"}]},{cN:"string",c:[e.BE],v:[e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},{cN:"number",v:[e.BNM,e.CNM]}]}});hljs.registerLanguage("ini",function(e){var c={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"title",b:/^\s*\[+/,e:/\]+/},{cN:"setting",b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",c:[{cN:"value",eW:!0,k:"on off true false yes no",c:[{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},c,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM],r:0}]}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"chunk",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("dos",function(e){var r=e.C(/@?rem\b/,/$/,{r:10}),t={cN:"label",b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",r:0};return{aliases:["bat","cmd"],cI:!0,k:{flow:"if else goto for in do call exit not exist errorlevel defined",operator:"equ neq lss leq gtr geq",keyword:"shift cd dir echo setlocal endlocal set pause copy",stream:"prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux",winutils:"ping net ipconfig taskkill xcopy ren del",built_in:"append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color comp compact convert date dir diskcomp diskcopy doskey erase fs find findstr format ftype graftabl help keyb label md mkdir mode more move path pause print popd pushd promt rd recover rem rename replace restore rmdir shiftsort start subst time title tree type ver verify vol"},c:[{cN:"envvar",b:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{cN:"function",b:t.b,e:"goto:eof",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),r]},{cN:"number",b:"\\b\\d+",r:0},r]}});hljs.registerLanguage("erlang-repl",function(r){return{k:{special_functions:"spawn spawn_link self",reserved:"after and andalso|10 band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse|10 query receive rem try when xor"},c:[{cN:"prompt",b:"^[0-9]+> ",r:10},r.C("%","$"),{cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},r.ASM,r.QSM,{cN:"constant",b:"\\?(::)?([A-Z]\\w*(::)?)+"},{cN:"arrow",b:"->"},{cN:"ok",b:"ok"},{cN:"exclamation_mark",b:"!"},{cN:"function_or_atom",b:"(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)",r:0},{cN:"variable",b:"[A-Z][a-zA-Z0-9_']*",r:0}]}});hljs.registerLanguage("groovy",function(e){return{k:{typename:"byte short char int long boolean float double void",literal:"true false null",keyword:"def as in assert trait super this abstract static volatile transient public private protected synchronized final class interface enum if else for while switch case break default continue throw throws try catch finally implements extends new import package return instanceof"},c:[e.C("/\\*\\*","\\*/",{r:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,{cN:"string",b:'"""',e:'"""'},{cN:"string",b:"'''",e:"'''"},{cN:"string",b:"\\$/",e:"/\\$",r:10},e.ASM,{cN:"regexp",b:/~?\/[^\/\n]+\//,c:[e.BE]},e.QSM,{cN:"shebang",b:"^#!/usr/bin/env",e:"$",i:"\n"},e.BNM,{cN:"class",bK:"class interface trait enum",e:"{",i:":",c:[{bK:"extends implements"},e.UTM]},e.CNM,{cN:"annotation",b:"@[A-Za-z]+"},{cN:"string",b:/[^\?]{0}[A-Za-z0-9_$]+ *:/},{b:/\?/,e:/\:/},{cN:"label",b:"^\\s*[A-Za-z0-9_$]+:",r:0}],i:/#/}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer",constant:"true false iota nil",typename:"bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},m="",O="Could not find the language '{}', did you forget to load/include a language module?",B={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},c="of and for in not or if then".split(" ");function x(e){return e.replace(/&/g,"&").replace(//g,">")}function d(e){return e.nodeName.toLowerCase()}function R(e){return n.test(e)}function i(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function p(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),d(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function v(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""}function u(e){("start"===e.event?c:l)(e.node)}for(;e.length||n.length;){var s=o();if(a+=x(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);u(s.splice(0,1)[0]),(s=o())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(c)}else"start"===s[0].event?i.push(s[0].node):i.pop(),u(s.splice(0,1)[0])}return a+x(t.substr(r))}function l(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return i(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[i(n,{starts:n.starts?i(n.starts):null})]:Object.isFrozen(n)?[i(n)]:[n]}function u(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(u)}}function M(n,t){var i={};return"string"==typeof n?r("keyword",n):o(n).forEach(function(e){r(e,n[e])}),i;function r(a,e){t&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n,t,r=e.split("|");i[r[0]]=[a,(n=r[0],(t=r[1])?Number(t):function(e){return-1!=c.indexOf(e.toLowerCase())}(n)?0:1)]})}}function S(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,o={},c=[],l={},t=1;function n(e,n){o[t]=e,c.push([e,n]),t+=new RegExp(n.toString()+"|").exec("").length-1+1}for(var r=0;r')+n+(t?"":m)}function o(){p+=(null!=d.sL?function(){var e="string"==typeof d.sL;if(e&&!_[d.sL])return x(v);var n=e?T(d.sL,v,!0,R[d.sL]):w(v,d.sL.length?d.sL:void 0);return 0")+'"');if("end"===n.type){var r=s(n);if(null!=r)return r}return v+=t,t.length}var g=D(n);if(!g)throw console.error(O.replace("{}",n)),new Error('Unknown language: "'+n+'"');S(g);var E,d=t||g,R={},p="";for(E=d;E!==g;E=E.parent)E.cN&&(p=c(E.cN,"",!0)+p);var v="",M=0;try{for(var b,h,N=0;d.t.lastIndex=N,b=d.t.exec(i);)h=r(i.substring(N,b.index),b),N=b.index+h;for(r(i.substr(N)),E=d;E.parent;E=E.parent)E.cN&&(p+=m);return{relevance:M,value:p,i:!1,language:n,top:d}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:x(i)};if(C)return{relevance:0,value:x(i),language:n,top:d,errorRaised:e};throw e}}function w(t,e){e=e||B.languages||o(_);var r={relevance:0,value:x(t)},a=r;return e.filter(D).filter(L).forEach(function(e){var n=T(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function b(e){return B.tabReplace||B.useBR?e.replace(t,function(e,n){return B.useBR&&"\n"===e?"
":B.tabReplace?n.replace(/\t/g,B.tabReplace):""}):e}function s(e){var n,t,r,a,i,o,c,l,u,s,f=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=E.exec(i)){var o=D(t[1]);return o||(console.warn(O.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),o?t[1]:"no-highlight"}for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=f?T(f,i,!0):w(i),(t=p(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=v(t,p(a),i)),r.value=b(r.value),e.innerHTML=r.value,e.className=(o=e.className,c=f,l=r.language,u=c?g[c]:l,s=[o.trim()],o.match(/\bhljs\b/)||s.push("hljs"),-1===o.indexOf(u)&&s.push(u),s.join(" ").trim()),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,s)}}var N={disableAutodetect:!0};function D(e){return e=(e||"").toLowerCase(),_[e]||_[g[e]]}function L(e){var n=D(e);return n&&!n.disableAutodetect}return a.highlight=T,a.highlightAuto=w,a.fixMarkup=b,a.highlightBlock=s,a.configure=function(e){B=i(B,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t;try{t=e(a)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!C)throw e;console.error(e),t=N}u(_[n]=t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){g[e]=n})},a.listLanguages=function(){return o(_)},a.getLanguage=D,a.requireLanguage=function(e){var n=D(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},a.autoDetection=L,a.inherit=i,a.debugMode=function(){C=!1},a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},[a.BE,a.ASM,a.QSM,a.PWM,a.C,a.CLCM,a.CBCM,a.HCM,a.NM,a.CNM,a.BNM,a.CSSNM,a.RM,a.TM,a.UTM,a.METHOD_GUARD].forEach(function(e){!function n(t){Object.freeze(t);var r="function"==typeof t;Object.getOwnPropertyNames(t).forEach(function(e){!t.hasOwnProperty(e)||null===t[e]||"object"!=typeof t[e]&&"function"!=typeof t[e]||r&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(t[e])||n(t[e])});return t}(e)}),a});hljs.registerLanguage("erlang",function(e){var r="[a-z'][a-zA-Z0-9_']*",c="("+r+":"+r+"|"+r+")",n={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"},a=e.C("%","$"),b={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",relevance:0},i={b:"fun\\s+"+r+"/\\d+"},l={b:c+"\\(",e:"\\)",rB:!0,relevance:0,c:[{b:c,relevance:0},{b:"\\(",e:"\\)",eW:!0,rE:!0,relevance:0}]},d={b:"{",e:"}",relevance:0},o={b:"\\b_([A-Z][A-Za-z0-9_]*)?",relevance:0},t={b:"[A-Z][a-zA-Z0-9_]*",relevance:0},v={b:"#"+e.UIR,relevance:0,rB:!0,c:[{b:"#"+e.UIR,relevance:0},{b:"{",e:"}",relevance:0}]},f={bK:"fun receive if try case",e:"end",k:n};f.c=[a,i,e.inherit(e.ASM,{cN:""}),f,l,e.QSM,b,d,o,t,v];var s=[a,i,f,l,e.QSM,b,d,o,t,v];l.c[1].c=s,d.c=s;var u={cN:"params",b:"\\(",e:"\\)",c:v.c[1].c=s};return{aliases:["erl"],k:n,i:"(",rB:!0,i:"\\(|#|//|/\\*|\\\\|:|;",c:[u,e.inherit(e.TM,{b:r})],starts:{e:";|\\.",k:n,c:s}},a,{b:"^-",e:"\\.",relevance:0,eE:!0,rB:!0,l:"-"+e.IR,k:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec",c:[u]},b,e.QSM,v,o,t,d,{b:/\.$/}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},a={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,relevance:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],relevance:0},e.HCM,a,{cN:"",b:/\\"/},{cN:"string",b:/'/,e:/'/},t]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("scheme",function(e){var t="[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",r="(\\-|\\+)?\\d+([./]\\d+)?",a={cN:"literal",b:"(#t|#f|#\\\\"+t+"|#\\\\.)"},i={cN:"number",v:[{b:r,relevance:0},{b:"(\\-|\\+)?\\d+([./]\\d+)?[+\\-](\\-|\\+)?\\d+([./]\\d+)?i",relevance:0},{b:"#b[0-1]+(/[0-1]+)?"},{b:"#o[0-7]+(/[0-7]+)?"},{b:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},n=e.QSM,c=[e.C(";","$",{relevance:0}),e.C("#\\|","\\|#")],l={b:t,relevance:0},s={cN:"symbol",b:"'"+t},o={eW:!0,relevance:0},u={v:[{b:/'/},{b:"`"}],c:[{b:"\\(",e:"\\)",c:["self",a,n,i,l,s]}]},p={cN:"name",b:t,l:t,k:{"builtin-name":"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"}},d={v:[{b:"\\(",e:"\\)"},{b:"\\[",e:"\\]"}],c:[{b:/lambda/,eW:!0,rB:!0,c:[p,{b:/\(/,e:/\)/,endsParent:!0,c:[l]}]},p,o]};return o.c=[a,i,n,l,s,u,d].concat(c),{i:/\S/,c:[{cN:"meta",b:"^#!",e:"$"},i,n,s,u,d].concat(c)}});hljs.registerLanguage("basic",function(E){return{cI:!0,i:"^.",l:"[a-zA-Z][a-zA-Z0-9_$%!#]*",k:{keyword:"ABS ASC AND ATN AUTO|0 BEEP BLOAD|10 BSAVE|10 CALL CALLS CDBL CHAIN CHDIR CHR$|10 CINT CIRCLE CLEAR CLOSE CLS COLOR COM COMMON CONT COS CSNG CSRLIN CVD CVI CVS DATA DATE$ DEFDBL DEFINT DEFSNG DEFSTR DEF|0 SEG USR DELETE DIM DRAW EDIT END ENVIRON ENVIRON$ EOF EQV ERASE ERDEV ERDEV$ ERL ERR ERROR EXP FIELD FILES FIX FOR|0 FRE GET GOSUB|10 GOTO HEX$ IF THEN ELSE|0 INKEY$ INP INPUT INPUT# INPUT$ INSTR IMP INT IOCTL IOCTL$ KEY ON OFF LIST KILL LEFT$ LEN LET LINE LLIST LOAD LOC LOCATE LOF LOG LPRINT USING LSET MERGE MID$ MKDIR MKD$ MKI$ MKS$ MOD NAME NEW NEXT NOISE NOT OCT$ ON OR PEN PLAY STRIG OPEN OPTION BASE OUT PAINT PALETTE PCOPY PEEK PMAP POINT POKE POS PRINT PRINT] PSET PRESET PUT RANDOMIZE READ REM RENUM RESET|0 RESTORE RESUME RETURN|0 RIGHT$ RMDIR RND RSET RUN SAVE SCREEN SGN SHELL SIN SOUND SPACE$ SPC SQR STEP STICK STOP STR$ STRING$ SWAP SYSTEM TAB TAN TIME$ TIMER TROFF TRON TO USR VAL VARPTR VARPTR$ VIEW WAIT WHILE WEND WIDTH WINDOW WRITE XOR"},c:[E.QSM,E.C("REM","$",{relevance:10}),E.C("'","$",{relevance:0}),{cN:"symbol",b:"^[0-9]+ ",relevance:10},{cN:"number",b:"\\b([0-9]+[0-9edED.]*[#!]?)",relevance:0},{cN:"number",b:"(&[hH][0-9a-fA-F]{1,4})"},{cN:"number",b:"(&[oO][0-7]{1,6})"}]}});hljs.registerLanguage("swift",function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t=e.C("/\\*","\\*/",{c:["self"]}),n={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},r={cN:"string",c:[e.BE,n],v:[{b:/"""/,e:/"""/},{b:/"/,e:/"/}]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return n.c=[a],{k:i,c:[r,e.CLCM,t,{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},a,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b://},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",a,r,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{bK:"import",e:/$/,c:[e.CLCM,t]}]}});hljs.registerLanguage("ini",function(e){var b={cN:"number",relevance:0,v:[{b:/([\+\-]+)?[\d]+_[\d_]+/},{b:e.NR}]},a=e.C();a.v=[{b:/;/,e:/$/},{b:/#/,e:/$/}];var c={cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"literal",b:/\bon|off|true|false|yes|no\b/},n={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",relevance:10},{b:'"""',e:'"""',relevance:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[a,{cN:"section",b:/\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_\.-]+(?=\s*=\s*)/,cN:"attr",starts:{e:/$/,c:[a,{b:/\[/,e:/\]/,c:[a,r,c,n,b,"self"],relevance:0},r,c,n,b]}}]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",b={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},r={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},n=[e.C("#","$",{c:[r]}),e.C("^\\=begin","^\\=end",{c:[r],relevance:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:b},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,rB:!0,c:[{b:/<<[-~]?'?/},{b:/\w+/,endSameAsBegin:!0,c:[e.BE,s]}]}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:b},l=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",relevance:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:c}],relevance:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:b},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),relevance:0}].concat(n);s.c=l;var d=[{b:/^\s*=>/,starts:{e:"$",c:i.c=l}},{cN:"meta",b:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:b,i:/\/\*/,c:n.concat(d).concat(l)}});hljs.registerLanguage("plaintext",function(e){return{disableAutodetect:!0}});hljs.registerLanguage("d",function(e){var a="(0|[1-9][\\d_]*)",d="("+a+"|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",t="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",_={cN:"number",b:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},r={cN:"number",b:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},i={cN:"string",b:"'("+t+"|.)",e:"'",i:"."},n={cN:"string",b:'"',c:[{b:t,relevance:0}],e:'"[cwd]?'},c=e.C("\\/\\+","\\+\\/",{c:["self"],relevance:10});return{l:e.UIR,k:{keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},c:[e.CLCM,e.CBCM,c,{cN:"string",b:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},n,{cN:"string",b:'[rq]"',e:'"[cwd]?',relevance:5},{cN:"string",b:"`",e:"`[cwd]?"},{cN:"string",b:'q"\\{',e:'\\}"'},r,_,i,{cN:"meta",b:"^#!",e:"$",relevance:5},{cN:"meta",b:"#(line)",e:"$",relevance:5},{cN:"keyword",b:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}});hljs.registerLanguage("scala",function(e){var t={cN:"subst",v:[{b:"\\$[A-Za-z0-9_]+"},{b:"\\${",e:"}"}]},a={cN:"string",v:[{b:'"',e:'"',i:"\\n",c:[e.BE]},{b:'"""',e:'"""',relevance:10},{b:'[a-z]+"',e:'"',i:"\\n",c:[e.BE,t]},{cN:"string",b:'[a-z]+"""',e:'"""',c:[t],relevance:10}]},c={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},r={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},n={cN:"class",bK:"class object trait type",e:/[:={\[\n;]/,eE:!0,c:[{bK:"extends with",relevance:10},{b:/\[/,e:/\]/,eB:!0,eE:!0,relevance:0,c:[c]},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,relevance:0,c:[c]},r]},l={cN:"function",bK:"def",e:/[:={\[(\n;]/,eE:!0,c:[r]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,a,{cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},c,l,n,e.CNM,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("cs",function(e){var a={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},c={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},r=e.inherit(c,{i:/\n/}),n={cN:"subst",b:"{",e:"}",k:a},t=e.inherit(n,{i:/\n/}),s={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,t]},l={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},n]},b=e.inherit(l,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},t]});n.c=[l,s,c,e.ASM,e.QSM,i,e.CBCM],t.c=[b,s,r,e.ASM,e.QSM,i,e.inherit(e.CBCM,{i:/\n/})];var o={v:[l,s,c,e.ASM,e.QSM]},d=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp","c#"],k:a,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",relevance:0},{b:"\x3c!--|--\x3e"},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},o,i,{bK:"class interface",e:/[{;=]/,i:/[^\s:,]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",relevance:0},{cN:"function",b:"("+d+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/\s*[{;=]/,eE:!0,k:a,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],relevance:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:a,relevance:0,c:[o,i,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("cpp",function(e){function t(e){return"(?:"+e+")?"}var r="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",i=(t(a),t("<.*?>"),{cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"}),c={cN:"string",v:[{b:'(u8?|U|L)?"',e:'"',i:"\\n",c:[e.BE]},{b:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",e:"'",i:"."},{b:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},n={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},c:[{b:/\\\n/,relevance:0},e.inherit(c,{cN:"meta-string"}),{cN:"meta-string",b:/<.*?>/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},o={cN:"title",b:t(a)+e.IR,relevance:0},l=t(a)+e.IR+"\\s*\\(",u={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_tshort reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},p=[i,e.CLCM,e.CBCM,s,c],m={v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:u,c:p.concat([{b:/\(/,e:/\)/,k:u,c:p.concat(["self"]),relevance:0}]),relevance:0},d={cN:"function",b:"((decltype\\(auto\\)|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+"+l,rB:!0,e:/[{;=]/,eE:!0,k:u,i:/[^\w\s\*&:<>]/,c:[{b:r,k:u,relevance:0},{b:l,rB:!0,c:[o],relevance:0},{cN:"params",b:/\(/,e:/\)/,k:u,relevance:0,c:[e.CLCM,e.CBCM,c,s,i,{b:/\(/,e:/\)/,k:u,relevance:0,c:["self",e.CLCM,e.CBCM,c,s,i]}]},i,e.CLCM,e.CBCM,n]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],k:u,i:"",k:u,c:["self",i]},{b:e.IR+"::",k:u},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},e.TM]}]),exports:{preprocessor:n,strings:c,k:u}}});hljs.registerLanguage("less",function(e){function r(e){return{cN:"string",b:"~?"+e+".*?"+e}}function t(e,r,t){return{cN:e,b:r,relevance:t}}var a="[\\w-]+",c="("+a+"|@{"+a+"})",s=[],n=[],b={b:"\\(",e:"\\)",c:n,relevance:0};n.push(e.CLCM,e.CBCM,r("'"),r('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},t("number","#[0-9A-Fa-f]+\\b"),b,t("variable","@@?"+a,10),t("variable","@{"+a+"}"),t("built_in","~?`[^`]*?`"),{cN:"attribute",b:a+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var i=n.concat({b:"{",e:"}",c:s}),l={bK:"when",eW:!0,c:[{bK:"and not"}].concat(n)},o={b:c+"\\s*:",rB:!0,e:"[;}]",relevance:0,c:[{cN:"attribute",b:c,e:":",eE:!0,starts:{eW:!0,i:"[<=$]",relevance:0,c:n}}]},u={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:n,relevance:0}},v={cN:"variable",v:[{b:"@"+a+"\\s*:",relevance:15},{b:"@"+a}],starts:{e:"[;}]",rE:!0,c:i}},C={v:[{b:"[\\.#:&\\[>]",e:"[;{}]"},{b:c,e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",relevance:0,c:[e.CLCM,e.CBCM,l,t("keyword","all\\b"),t("variable","@{"+a+"}"),t("selector-tag",c+"%?",0),t("selector-id","#"+c),t("selector-class","\\."+c,0),t("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"\\(",e:"\\)",c:i},{b:"!important"}]};return s.push(e.CLCM,e.CBCM,u,v,o,C),{cI:!0,i:"[=>'/<($\"]",c:s}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={b:/\{\{/,relevance:0},l={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[e.BE,b],relevance:10},{b:/(u|b)?r?"""/,e:/"""/,c:[e.BE,b],relevance:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[e.BE,b,a,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[e.BE,b,a,c]},{b:/(u|r|ur)'/,e:/'/,relevance:10},{b:/(u|r|ur)"/,e:/"/,relevance:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[e.BE,a,c]},{b:/(fr|rf|f)"/,e:/"/,c:[e.BE,a,c]},e.ASM,e.QSM]},n={cN:"number",relevance:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,n,l,e.HCM]};return c.c=[l,n,b],{aliases:["py","gyp","ipython"],k:r,i:/(<\/|->|\?)|=>/,c:[b,n,{bK:"if",relevance:0},l,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("powershell",function(e){var t={keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter"},n={b:"`[\\s\\S]",relevance:0},c={cN:"variable",v:[{b:/\$\B/},{cN:"keyword",b:/\$this/},{b:/\$[\w\d][\w\d_:]*/}]},i={cN:"string",v:[{b:/"/,e:/"/},{b:/@"/,e:/^"@/}],c:[n,c,{cN:"variable",b:/\$[A-z]/,e:/[^A-z]/}]},a={cN:"string",v:[{b:/'/,e:/'/},{b:/@'/,e:/^'@/}]},r=e.inherit(e.C(null,null),{v:[{b:/#/,e:/$/},{b:/<#/,e:/#>/}],c:[{cN:"doctag",v:[{b:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{b:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),o={cN:"built_in",v:[{b:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|New|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Complete|Confirm|Deny|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},l={cN:"class",bK:"class enum",e:/\s*[{]/,eE:!0,relevance:0,c:[e.TM]},s={cN:"function",b:/function\s+/,e:/\s*\{|$/,eE:!0,rB:!0,relevance:0,c:[{b:"function",relevance:0,cN:"keyword"},{cN:"title",b:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{b:/\(/,e:/\)/,cN:"params",relevance:0,c:[c]}]},p={b:/using\s/,e:/$/,rB:!0,c:[i,a,{cN:"keyword",b:/(using|assembly|command|module|namespace|type)/}]},b={v:[{cN:"operator",b:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{cN:"literal",b:/(-)[\w\d]+/,relevance:0}]},d={cN:"function",b:/\[.*\]\s*[\w]+[ ]??\(/,e:/$/,rB:!0,relevance:0,c:[{cN:"keyword",b:"(".concat(t.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},e.inherit(e.TM,{endsParent:!0})]},u=[d,r,n,e.NM,i,a,o,c,{cN:"literal",b:/\$(null|true|false)\b/},{cN:"selector-tag",b:/\@\B/,relevance:0}],m={b:/\[/,e:/\]/,eB:!0,eE:!0,relevance:0,c:[].concat("self",u,{b:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",cN:"built_in",relevance:0},{cN:"type",b:/[\.\w\d]+/,relevance:0})};return d.c.unshift(m),{aliases:["ps","ps1"],l:/-?[A-z\.\-]+/,cI:!0,k:t,c:u.concat(l,s,p,b,m)}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",relevance:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]{0,3}(?=\\W)",relevance:0},{b:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",a={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,a]},{b:/[:\(,=]\s*/,relevance:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[a]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,relevance:0}])}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.CLCM,e.CBCM],c=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:c,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})].concat(n),i:"\\S"},a={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return c.push(t,a),n.forEach(function(e){c.push(e)}),{c:c,k:i,i:"\\S"}});hljs.registerLanguage("xml",function(e){var c={cN:"symbol",b:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},s={b:"\\s",c:[{cN:"meta-keyword",b:"#?[a-z_][a-z1-9_-]+",i:"\\n"}]},a=e.inherit(s,{b:"\\(",e:"\\)"}),t=e.inherit(e.ASM,{cN:"meta-string"}),l=e.inherit(e.QSM,{cN:"meta-string"}),r={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[s,l,t,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[s,a,l,t]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},c,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:")",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:")",e:">",k:{name:"script"},c:[r],starts:{e:"<\/script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,relevance:0},r]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",relevance:0},{cN:"bullet",b:"^\\s*([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",relevance:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```\\w*\\s*$",e:"^```[ ]*$"},{b:"`.+?`"},{b:"^( {4}|\\t)",e:"$",relevance:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,relevance:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],relevance:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},c:[{cN:"string",b:"'",e:"'",c:[{b:"''"}]},{cN:"string",b:'"',e:'"',c:[{b:'""'}]},{cN:"string",b:"`",e:"`"},e.CNM,e.CBCM,t,e.HCM]},e.CBCM,t,e.HCM]}});hljs.registerLanguage("javascript",function(e){var r="<>",a="",t={b:/<[A-Za-z0-9\\._:-]+/,e:/\/[A-Za-z0-9\\._:-]+>|\/>/},c="[A-Za-z$_][0-9A-Za-z$_]*",n={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:n,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},b={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},l={cN:"string",b:"`",e:"`",c:[e.BE,o]};o.c=[e.ASM,e.QSM,i,b,l,s,e.RM];var u=o.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx","mjs","cjs"],k:n,c:[{cN:"meta",relevance:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,i,b,l,e.CLCM,e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+",c:[{cN:"type",b:"\\{",e:"\\}",relevance:0},{cN:"variable",b:c+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{b:/(?=[^\n])\s/,relevance:0}]}]}),e.CBCM,s,{b:/[{,\n]\s*/,relevance:0,c:[{b:c+"\\s*:",rB:!0,relevance:0,c:[{cN:"attr",b:c,relevance:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+c+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:c},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:n,c:u}]}]},{cN:"",b:/\s/,e:/\s*/,skip:!0},{v:[{b:r,e:a},{b:t.b,e:t.e}],sL:"xml",c:[{b:t.b,e:t.e,skip:!0,c:["self"]}]}],relevance:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:c}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:u}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor get set",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,relevance:0}]},c=[e.BE,r,n],a=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:c,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",relevance:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",relevance:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",relevance:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",relevance:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",relevance:5},{b:"qw\\s+q",e:"q",relevance:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],relevance:0},{b:"-?\\w+\\s*\\=\\>",c:[],relevance:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",relevance:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],relevance:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,relevance:5,c:[e.TM]},{b:"-\\w\\b",relevance:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=a,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:s.c=a}});hljs.registerLanguage("tex",function(e){var c={cN:"tag",b:/\\/,relevance:0,c:[{cN:"name",v:[{b:/[a-zA-Z\u0430-\u044f\u0410-\u042f]+[*]?/},{b:/[^a-zA-Z\u0430-\u044f\u0410-\u042f0-9]/}],starts:{eW:!0,relevance:0,c:[{cN:"string",v:[{b:/\[/,e:/\]/},{b:/\{/,e:/\}/}]},{b:/\s*=\s*/,eW:!0,relevance:0,c:[{cN:"number",b:/-?\d*\.?\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?/}]}]}}]};return{c:[c,{cN:"formula",c:[c],relevance:0,v:[{b:/\$\$/,e:/\$\$/},{b:/\$/,e:/\$/}]},e.C("%","$",{relevance:0})]}});hljs.registerLanguage("fortran",function(e){return{cI:!0,aliases:["f90","f95"],k:{literal:".False. .True.",keyword:"kind do while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then block endblock public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure integer real character complex logical dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data",built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_ofacosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image"},i:/\/\*/,c:[e.inherit(e.ASM,{cN:"string",relevance:0}),e.inherit(e.QSM,{cN:"string",relevance:0}),{cN:"function",bK:"subroutine function program",i:"[${=\\n]",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)"}]},e.C("!","$",{relevance:0}),{cN:"number",b:"(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?",relevance:0}]}});hljs.registerLanguage("protobuf",function(e){return{k:{keyword:"package import option optional required repeated group oneof",built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",literal:"true false"},c:[e.QSM,e.NM,e.CLCM,{cN:"class",bK:"message enum service",e:/\{/,i:/\n/,c:[e.inherit(e.TM,{starts:{eW:!0,eE:!0}})]},{cN:"function",bK:"rpc",e:/;/,eE:!0,k:"rpc returns"},{b:/^\s*[A-Z_]+/,e:/\s*=/,eE:!0}]}});hljs.registerLanguage("java",function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",t={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0};return{aliases:["jsp"],k:a,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{b:/\w+@/,relevance:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",relevance:0},{cN:"function",b:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:a,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:a,relevance:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},t,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("properties",function(e){var r="[ \\t\\f]*",t="("+r+"[:=]"+r+"|[ \\t\\f]+)",n="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",c={e:t,relevance:0,starts:{cN:"string",e:/$/,relevance:0,c:[{b:"\\\\\\n"}]}};return{cI:!0,i:/\S/,c:[e.C("^\\s*[!#]","$"),{b:n+t,rB:!0,c:[{cN:"attr",b:n,endsParent:!0,relevance:0}],starts:c},{b:a+t,rB:!0,relevance:0,c:[{cN:"meta",b:a,endsParent:!0,relevance:0}],starts:c},{cN:"attr",relevance:0,b:a+r+"$"}]}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php","php3","php4","php5","php6","php7"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],relevance:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],relevance:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},{cN:"class",b:"("+i.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:i,l:t,c:[e.UTM]},{b:"\\."+e.UIR,relevance:0}]}});hljs.registerLanguage("typescript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},n={cN:"meta",b:"@"+r},a={b:"\\(",e:/\)/,k:t,c:["self",e.QSM,e.ASM,e.NM]},c={cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:[e.CLCM,e.CBCM,n,a]},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},l={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},b={cN:"string",b:"`",e:"`",c:[e.BE,o]};return o.c=[e.ASM,e.QSM,i,l,b,s,e.RM],{aliases:["ts"],k:t,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,i,l,b,e.CLCM,e.CBCM,s,{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:["self",e.CLCM,e.CBCM]}]}]}],relevance:0},{cN:"function",bK:"function",e:/[\{;]/,eE:!0,k:t,c:["self",e.inherit(e.TM,{b:r}),c],i:/%/,relevance:0},{bK:"constructor",e:/[\{;]/,eE:!0,c:["self",c]},{b:/module\./,k:{built_in:"module"},relevance:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,relevance:0},n,a]}});hljs.registerLanguage("elm",function(e){var i={v:[e.C("--","$"),e.C("{-","-}",{c:["self"]})]},t={cN:"type",b:"\\b[A-Z][\\w']*",relevance:0},c={b:"\\(",e:"\\)",i:'"',c:[{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},i]};return{k:"let in if then else case of where module import exposing type alias as infix infixl infixr port effect command subscription",c:[{bK:"port effect module",e:"exposing",k:"port effect module where command subscription exposing",c:[c,i],i:"\\W\\.|;"},{b:"import",e:"$",k:"import as exposing",c:[c,i],i:"\\W\\.|;"},{b:"type",e:"$",k:"type alias",c:[t,c,{b:"{",e:"}",c:c.c},i]},{bK:"infix infixl infixr",e:"$",c:[e.CNM,i]},{b:"port",e:"$",k:"port",c:[i]},{cN:"string",b:"'\\\\?.",e:"'",i:"."},e.QSM,e.CNM,t,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),i,{b:"->|<-"}],i:/;/}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",relevance:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",relevance:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/^\*{15}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("lisp",function(e){var b="[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*",c="\\|[^]*?\\|",a="(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",l={cN:"literal",b:"\\b(t{1}|nil)\\b"},n={cN:"number",v:[{b:a,relevance:0},{b:"#(b|B)[0-1]+(/[0-1]+)?"},{b:"#(o|O)[0-7]+(/[0-7]+)?"},{b:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},{b:"#(c|C)\\("+a+" +"+a,e:"\\)"}]},r=e.inherit(e.QSM,{i:null}),i=e.C(";","$",{relevance:0}),t={b:"\\*",e:"\\*"},v={cN:"symbol",b:"[:&]"+b},s={b:b,relevance:0},u={b:c},d={c:[n,r,t,v,{b:"\\(",e:"\\)",c:["self",l,r,n,s]},s],v:[{b:"['`]\\(",e:"\\)"},{b:"\\(quote ",e:"\\)",k:{name:"quote"}},{b:"'"+c}]},f={v:[{b:"'"+b},{b:"#'"+b+"(::"+b+")*"}]},m={b:"\\(\\s*",e:"\\)"},o={eW:!0,relevance:0};return m.c=[{cN:"name",v:[{b:b},{b:c}]},o],o.c=[d,f,m,l,n,r,i,t,v,u,s],{i:/\S/,c:[n,{cN:"meta",b:"^#!",e:"$"},l,r,i,d,f,m,s]}});hljs.registerLanguage("kotlin",function(e){var t={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={cN:"symbol",b:e.UIR+"@"},n={cN:"subst",b:"\\${",e:"}",c:[e.CNM]},c={cN:"variable",b:"\\$"+e.UIR},r={cN:"string",v:[{b:'"""',e:'"""(?=[^"])',c:[c,n]},{b:"'",e:"'",i:/\n/,c:[e.BE]},{b:'"',e:'"',i:/\n/,c:[e.BE,c,n]}]};n.c.push(r);var i={cN:"meta",b:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UIR+")?"},l={cN:"meta",b:"@"+e.UIR,c:[{b:/\(/,e:/\)/,c:[e.inherit(r,{cN:"meta-string"})]}]},s={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},b=e.C("/\\*","\\*/",{c:[e.CBCM]}),o={v:[{cN:"type",b:e.UIR},{b:/\(/,e:/\)/,c:[]}]},d=o;return d.v[1].c=[o],o.v[1].c=[d],{aliases:["kt"],k:t,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,b,{cN:"keyword",b:/\b(break|continue|return|this)\b/,starts:{c:[{cN:"symbol",b:/@\w+/}]}},a,i,l,{cN:"function",bK:"fun",e:"[(]|$",rB:!0,eE:!0,k:t,i:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"type",b://,k:"reified",relevance:0},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:t,relevance:0,c:[{b:/:/,e:/[=,\/]/,eW:!0,c:[o,e.CLCM,b],relevance:0},e.CLCM,b,i,l,r,e.CNM]},b]},{cN:"class",bK:"class interface trait",e:/[:\{(]|$/,eE:!0,i:"extends implements",c:[{bK:"public protected internal private constructor"},e.UTM,{cN:"type",b://,eB:!0,eE:!0,relevance:0},{cN:"type",b:/[,:]\s*/,e:/[<\(,]|$/,eB:!0,rE:!0},i,l]},r,{cN:"meta",b:"^#!/usr/bin/env",e:"$",i:"\n"},s]}});hljs.registerLanguage("dart",function(e){var t={cN:"subst",v:[{b:"\\$[A-Za-z0-9_]+"}]},r={cN:"subst",v:[{b:"\\${",e:"}"}],k:"true false null this is new super"},n={cN:"string",v:[{b:"r'''",e:"'''"},{b:'r"""',e:'"""'},{b:"r'",e:"'",i:"\\n"},{b:'r"',e:'"',i:"\\n"},{b:"'''",e:"'''",c:[e.BE,t,r]},{b:'"""',e:'"""',c:[e.BE,t,r]},{b:"'",e:"'",i:"\\n",c:[e.BE,t,r]},{b:'"',e:'"',i:"\\n",c:[e.BE,t,r]}]};r.c=[e.CNM,n];return{k:{keyword:"abstract as assert async await break case catch class const continue covariant default deferred do dynamic else enum export extends extension external factory false final finally for Function get hide if implements import in inferface is library mixin new null on operator part rethrow return set show static super switch sync this throw true try typedef var void while with yield",built_in:"Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double dynamic int num print Element ElementList document querySelector querySelectorAll window"},c:[n,e.C("/\\*\\*","\\*/",{sL:"markdown"}),e.C("///+\\s*","$",{c:[{sL:"markdown",b:".",e:"$"}]}),e.CLCM,e.CBCM,{cN:"class",bK:"class interface",e:"{",eE:!0,c:[{bK:"extends implements"},e.UTM]},e.CNM,{cN:"meta",b:"@[A-Za-z]+"},{b:"=>"}]}});hljs.registerLanguage("applescript",function(e){var t=e.inherit(e.QSM,{i:""}),r={cN:"params",b:"\\(",e:"\\)",c:["self",e.CNM,t]},i=e.C("--","$"),o=[i,e.C("\\(\\*","\\*\\)",{c:["self",i]}),e.HCM];return{aliases:["osascript"],k:{keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",literal:"AppleScript false linefeed return pi quote result space tab true",built_in:"alias application boolean class constant date file integer list number real record string text activate beep count delay launch log offset read round run say summarize write character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"},c:[t,e.CNM,{cN:"built_in",b:"\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b"},{cN:"literal",b:"\\b(text item delimiters|current application|missing value)\\b"},{cN:"keyword",b:"\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference)|POSIX file|POSIX path|(date|time) string|quoted form)\\b"},{bK:"on",i:"[${=;\\n]",c:[e.UTM,r]}].concat(o),i:"//|->|=>|\\[\\["}});hljs.registerLanguage("autohotkey",function(e){var a={b:"`[\\s\\S]"};return{cI:!0,aliases:["ahk"],k:{keyword:"Break Continue Critical Exit ExitApp Gosub Goto New OnExit Pause return SetBatchLines SetTimer Suspend Thread Throw Until ahk_id ahk_class ahk_pid ahk_exe ahk_group",literal:"true false NOT AND OR",built_in:"ComSpec Clipboard ClipboardAll ErrorLevel"},c:[a,e.inherit(e.QSM,{c:[a]}),e.C(";","$",{relevance:0}),e.CBCM,{cN:"number",b:e.NR,relevance:0},{cN:"variable",b:"%[a-zA-Z0-9#_$@]+%"},{cN:"built_in",b:"^\\s*\\w+\\s*(,|%)"},{cN:"title",v:[{b:'^[^\\n";]+::(?!=)'},{b:'^[^\\n";]+:(?!=)',relevance:0}]},{cN:"meta",b:"^\\s*#\\w+",e:"$",relevance:0},{cN:"built_in",b:"A_[a-zA-Z0-9]+"},{b:",\\s*,"}]}});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:r},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("scss",function(e){var t="@[a-z-]+",r={cN:"variable",b:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={cN:"number",b:"#[0-9A-Fa-f]+"};e.CSSNM,e.QSM,e.ASM,e.CBCM;return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",relevance:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",relevance:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{cN:"selector-pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"selector-pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},r,{cN:"attribute",b:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[r,i,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@(page|font-face)",l:t,k:"@page @font-face"},{b:"@",e:"[{;]",rB:!0,k:"and or not only",c:[{b:t,cN:"keyword"},r,e.QSM,e.ASM,i,e.CSSNM]}]}});hljs.registerLanguage("dockerfile",function(e){return{aliases:["docker"],cI:!0,k:"from maintainer expose env arg user onbuild stopsignal",c:[e.HCM,e.ASM,e.QSM,e.NM,{bK:"run cmd entrypoint volume add copy workdir label healthcheck shell",starts:{e:/[^\\]$/,sL:"bash"}}],i:""},{cN:"attribute",b:/\w+/,relevance:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,relevance:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("prolog",function(e){var c={b:/\(/,e:/\)/,relevance:0},b={b:/\[/,e:/\]/},n={cN:"comment",b:/%/,e:/$/,c:[e.PWM]},r={cN:"string",b:/`/,e:/`/,c:[e.BE]},a=[{b:/[a-z][A-Za-z0-9_]*/,relevance:0},{cN:"symbol",v:[{b:/[A-Z][a-zA-Z0-9_]*/},{b:/_[A-Za-z0-9_]*/}],relevance:0},c,{b:/:-/},b,n,e.CBCM,e.QSM,e.ASM,r,{cN:"string",b:/0\'(\\\'|.)/},{cN:"string",b:/0\'\\s/},e.CNM];return c.c=a,{c:(b.c=a).concat([{b:/\.$/}])}});hljs.registerLanguage("matlab",function(e){var a="('|\\.')+",s={relevance:0,c:[{b:a}]};return{k:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson max min nanmax nanmin mean nanmean type table readtable writetable sortrows sort figure plot plot3 scatter scatter3 cellfun legend intersect ismember procrustes hold num2cell "},i:'(//|"|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function",e:"$",c:[e.UTM,{cN:"params",v:[{b:"\\(",e:"\\)"},{b:"\\[",e:"\\]"}]}]},{cN:"built_in",b:/true|false/,relevance:0,starts:s},{b:"[a-zA-Z][a-zA-Z_0-9]*"+a,relevance:0},{cN:"number",b:e.CNR,relevance:0,starts:s},{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{b:/\]|}|\)/,relevance:0,starts:s},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}],starts:s},e.C("^\\s*\\%\\{\\s*$","^\\s*\\%\\}\\s*$"),e.C("\\%","$")]}});hljs.registerLanguage("tcl",function(e){return{aliases:["tk"],k:"after append apply array auto_execok auto_import auto_load auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock close concat continue dde dict encoding eof error eval exec exit expr fblocked fconfigure fcopy file fileevent filename flush for foreach format gets glob global history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename return safe scan seek set socket source split string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update uplevel upvar variable vwait while",c:[e.C(";[ \\t]*#","$"),e.C("^[ \\t]*#","$"),{bK:"proc",e:"[\\{]",eE:!0,c:[{cN:"title",b:"[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"[ \\t\\n\\r]",eW:!0,eE:!0}]},{eE:!0,v:[{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*\\(([a-zA-Z0-9_])*\\)",e:"[^a-zA-Z0-9_\\}\\$]"},{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"(\\))?[^a-zA-Z0-9_\\}\\$]"}]},{cN:"string",c:[e.BE],v:[e.inherit(e.QSM,{i:null})]},{cN:"number",v:[e.BNM,e.CNM]}]}});hljs.registerLanguage("go",function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:n,i:"]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,relevance:0},{cN:"type",b:"!"+e.UIR},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"\\-(?=[ ]|$)",relevance:0},e.HCM,{bK:b,k:{literal:b}},{cN:"number",b:e.CNR+"\\b"},a]}}); \ No newline at end of file diff --git a/util.go b/util.go index 3074ebc..dabe227 100644 --- a/util.go +++ b/util.go @@ -1,15 +1,10 @@ package main -func extensionToHlAndAceLangs(extension string) (hlExt, aceExt string) { +func extensionToHlLang(extension string) (hlExt string) { hlExt, exists := extensionToHl[extension] if !exists { hlExt = "text" } - - aceExt, exists = extensionToAce[extension] - if !exists { - aceExt = "text" - } return } @@ -18,83 +13,63 @@ func supportedBinExtension(extension string) bool { return exists } -var extensionToAce = map[string]string{ - "c": "c_cpp", - "h": "c_cpp", - "cpp": "c_cpp", - "clj": "clojure", - "coffee": "coffee", - "cfc": "coldfusion", - "cs": "csharp", - "sh": "sh", - "bash": "sh", - "css": "css", - "go": "golang", - "diff": "diff", - "html": "html", - "xml": "xml", - "ini": "ini", - "java": "java", - "js": "javascript", - "json": "json", - "jsp": "jsp", - "tex": "latex", - "lisp": "lisp", - "less": "less", - "lua": "lua", - "md": "markdown", - "ocaml": "ocaml", - "tcl": "tcl", - "yaml": "yaml", - "php": "php", - "pl": "perl", - "py": "python", - "rb": "ruby", - "sql": "sql", - "apache": "apache", - "cmake": "cmake", - "bat": "dos", - "scala": "scala", - "txt": "text", -} - var extensionToHl = map[string]string{ - "c": "cpp", - "h": "cpp", - "cpp": "c_cpp", - "clj": "clojure", - "coffee": "coffee", - "cfc": "coldfusion", - "cs": "csharp", - "sh": "sh", - "bash": "sh", - "css": "css", - "go": "go", - "diff": "diff", - "html": "html", - "htm": "html", - "ini": "ini", - "java": "java", - "js": "javascript", - "json": "json", - "jsp": "jsp", - "tex": "latex", - "lisp": "lisp", - "less": "less", - "lua": "lua", - "ocaml": "ocaml", - "tcl": "tcl", - "nginx": "nginx", - "xml": "xml", - "yaml": "yaml", - "php": "php", - "pl": "perl", - "py": "python", - "rb": "ruby", - "sql": "sql", - "apache": "apache", - "cmake": "cmake", - "bat": "dos", - "scala": "scala", - "txt": "text", + "ahk": "autohotkey", + "apache": "apache", + "applescript": "applescript", + "bas": "basic", + "bash": "sh", + "bat": "dos", + "c": "cpp", + "cfc": "coldfusion", + "clj": "clojure", + "cmake": "cmake", + "coffee": "coffee", + "cpp": "c_cpp", + "cs": "csharp", + "css": "css", + "d": "d", + "dart": "dart", + "diff": "diff", + "dockerfile": "dockerfile", + "elm": "elm", + "erl": "erlang", + "for": "fortran", + "go": "go", + "h": "cpp", + "htm": "html", + "html": "html", + "ini": "ini", + "java": "java", + "js": "javascript", + "json": "json", + "jsp": "jsp", + "kt": "kotlin", + "less": "less", + "lisp": "lisp", + "lua": "lua", + "m": "objectivec", + "nginx": "nginx", + "ocaml": "ocaml", + "php": "php", + "pl": "perl", + "proto": "protobuf", + "ps": "powershell", + "py": "python", + "rb": "ruby", + "rs": "rust", + "scala": "scala", + "scm": "scheme", + "scpt": "applescript", + "scss": "scss", + "sh": "sh", + "sql": "sql", + "tcl": "tcl", + "tex": "latex", + "toml": "ini", + "ts": "typescript", + "txt": "text", + "xml": "xml", + "yaml": "yaml", + "yml": "yaml", } From 49d45f52bb7a720b90b93c719a6de6528ca974b5 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 16:43:11 -0700 Subject: [PATCH 100/233] Highlight.js: Add license --- static/js/highlight/highlight.pack.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/js/highlight/highlight.pack.js b/static/js/highlight/highlight.pack.js index ea2203c..b235922 100644 --- a/static/js/highlight/highlight.pack.js +++ b/static/js/highlight/highlight.pack.js @@ -1,2 +1,3 @@ -/*! highlight.js v9.18.1 | BSD3 License | git.io/hljslicense */ -!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"==typeof exports||exports.nodeType?n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs})):e(exports)}(function(a){var f=[],o=Object.keys,_={},g={},C=!0,n=/^(no-?highlight|plain|text)$/i,E=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},m="",O="Could not find the language '{}', did you forget to load/include a language module?",B={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},c="of and for in not or if then".split(" ");function x(e){return e.replace(/&/g,"&").replace(//g,">")}function d(e){return e.nodeName.toLowerCase()}function R(e){return n.test(e)}function i(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function p(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),d(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function v(e,n,t){var r=0,a="",i=[];function o(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function l(e){a+=""}function u(e){("start"===e.event?c:l)(e.node)}for(;e.length||n.length;){var s=o();if(a+=x(t.substring(r,s[0].offset)),r=s[0].offset,s===e){for(i.reverse().forEach(l);u(s.splice(0,1)[0]),(s=o())===e&&s.length&&s[0].offset===r;);i.reverse().forEach(c)}else"start"===s[0].event?i.push(s[0].node):i.pop(),u(s.splice(0,1)[0])}return a+x(t.substr(r))}function l(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return i(n,{v:null},e)})),n.cached_variants?n.cached_variants:function e(n){return!!n&&(n.eW||e(n.starts))}(n)?[i(n,{starts:n.starts?i(n.starts):null})]:Object.isFrozen(n)?[i(n)]:[n]}function u(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(u)}}function M(n,t){var i={};return"string"==typeof n?r("keyword",n):o(n).forEach(function(e){r(e,n[e])}),i;function r(a,e){t&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n,t,r=e.split("|");i[r[0]]=[a,(n=r[0],(t=r[1])?Number(t):function(e){return-1!=c.indexOf(e.toLowerCase())}(n)?0:1)]})}}function S(r){function s(e){return e&&e.source||e}function f(e,n){return new RegExp(s(e),"m"+(r.cI?"i":"")+(n?"g":""))}function a(a){var i,e,o={},c=[],l={},t=1;function n(e,n){o[t]=e,c.push([e,n]),t+=new RegExp(n.toString()+"|").exec("").length-1+1}for(var r=0;r')+n+(t?"":m)}function o(){p+=(null!=d.sL?function(){var e="string"==typeof d.sL;if(e&&!_[d.sL])return x(v);var n=e?T(d.sL,v,!0,R[d.sL]):w(v,d.sL.length?d.sL:void 0);return 0")+'"');if("end"===n.type){var r=s(n);if(null!=r)return r}return v+=t,t.length}var g=D(n);if(!g)throw console.error(O.replace("{}",n)),new Error('Unknown language: "'+n+'"');S(g);var E,d=t||g,R={},p="";for(E=d;E!==g;E=E.parent)E.cN&&(p=c(E.cN,"",!0)+p);var v="",M=0;try{for(var b,h,N=0;d.t.lastIndex=N,b=d.t.exec(i);)h=r(i.substring(N,b.index),b),N=b.index+h;for(r(i.substr(N)),E=d;E.parent;E=E.parent)E.cN&&(p+=m);return{relevance:M,value:p,i:!1,language:n,top:d}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{i:!0,relevance:0,value:x(i)};if(C)return{relevance:0,value:x(i),language:n,top:d,errorRaised:e};throw e}}function w(t,e){e=e||B.languages||o(_);var r={relevance:0,value:x(t)},a=r;return e.filter(D).filter(L).forEach(function(e){var n=T(e,t,!1);n.language=e,n.relevance>a.relevance&&(a=n),n.relevance>r.relevance&&(a=r,r=n)}),a.language&&(r.second_best=a),r}function b(e){return B.tabReplace||B.useBR?e.replace(t,function(e,n){return B.useBR&&"\n"===e?"
":B.tabReplace?n.replace(/\t/g,B.tabReplace):""}):e}function s(e){var n,t,r,a,i,o,c,l,u,s,f=function(e){var n,t,r,a,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=E.exec(i)){var o=D(t[1]);return o||(console.warn(O.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),o?t[1]:"no-highlight"}for(n=0,r=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,r=f?T(f,i,!0):w(i),(t=p(n)).length&&((a=document.createElement("div")).innerHTML=r.value,r.value=v(t,p(a),i)),r.value=b(r.value),e.innerHTML=r.value,e.className=(o=e.className,c=f,l=r.language,u=c?g[c]:l,s=[o.trim()],o.match(/\bhljs\b/)||s.push("hljs"),-1===o.indexOf(u)&&s.push(u),s.join(" ").trim()),e.result={language:r.language,re:r.relevance},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.relevance}))}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");f.forEach.call(e,s)}}var N={disableAutodetect:!0};function D(e){return e=(e||"").toLowerCase(),_[e]||_[g[e]]}function L(e){var n=D(e);return n&&!n.disableAutodetect}return a.highlight=T,a.highlightAuto=w,a.fixMarkup=b,a.highlightBlock=s,a.configure=function(e){B=i(B,e)},a.initHighlighting=h,a.initHighlightingOnLoad=function(){window.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)},a.registerLanguage=function(n,e){var t;try{t=e(a)}catch(e){if(console.error("Language definition for '{}' could not be registered.".replace("{}",n)),!C)throw e;console.error(e),t=N}u(_[n]=t),t.rawDefinition=e.bind(null,a),t.aliases&&t.aliases.forEach(function(e){g[e]=n})},a.listLanguages=function(){return o(_)},a.getLanguage=D,a.requireLanguage=function(e){var n=D(e);if(n)return n;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},a.autoDetection=L,a.inherit=i,a.debugMode=function(){C=!1},a.IR=a.IDENT_RE="[a-zA-Z]\\w*",a.UIR=a.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",a.NR=a.NUMBER_RE="\\b\\d+(\\.\\d+)?",a.CNR=a.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",a.BNR=a.BINARY_NUMBER_RE="\\b(0b[01]+)",a.RSR=a.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",a.BE=a.BACKSLASH_ESCAPE={b:"\\\\[\\s\\S]",relevance:0},a.ASM=a.APOS_STRING_MODE={cN:"string",b:"'",e:"'",i:"\\n",c:[a.BE]},a.QSM=a.QUOTE_STRING_MODE={cN:"string",b:'"',e:'"',i:"\\n",c:[a.BE]},a.PWM=a.PHRASAL_WORDS_MODE={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},a.C=a.COMMENT=function(e,n,t){var r=a.inherit({cN:"comment",b:e,e:n,c:[]},t||{});return r.c.push(a.PWM),r.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),r},a.CLCM=a.C_LINE_COMMENT_MODE=a.C("//","$"),a.CBCM=a.C_BLOCK_COMMENT_MODE=a.C("/\\*","\\*/"),a.HCM=a.HASH_COMMENT_MODE=a.C("#","$"),a.NM=a.NUMBER_MODE={cN:"number",b:a.NR,relevance:0},a.CNM=a.C_NUMBER_MODE={cN:"number",b:a.CNR,relevance:0},a.BNM=a.BINARY_NUMBER_MODE={cN:"number",b:a.BNR,relevance:0},a.CSSNM=a.CSS_NUMBER_MODE={cN:"number",b:a.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},a.RM=a.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[a.BE,{b:/\[/,e:/\]/,relevance:0,c:[a.BE]}]},a.TM=a.TITLE_MODE={cN:"title",b:a.IR,relevance:0},a.UTM=a.UNDERSCORE_TITLE_MODE={cN:"title",b:a.UIR,relevance:0},a.METHOD_GUARD={b:"\\.\\s*"+a.UIR,relevance:0},[a.BE,a.ASM,a.QSM,a.PWM,a.C,a.CLCM,a.CBCM,a.HCM,a.NM,a.CNM,a.BNM,a.CSSNM,a.RM,a.TM,a.UTM,a.METHOD_GUARD].forEach(function(e){!function n(t){Object.freeze(t);var r="function"==typeof t;Object.getOwnPropertyNames(t).forEach(function(e){!t.hasOwnProperty(e)||null===t[e]||"object"!=typeof t[e]&&"function"!=typeof t[e]||r&&("caller"===e||"callee"===e||"arguments"===e)||Object.isFrozen(t[e])||n(t[e])});return t}(e)}),a});hljs.registerLanguage("erlang",function(e){var r="[a-z'][a-zA-Z0-9_']*",c="("+r+":"+r+"|"+r+")",n={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor",literal:"false true"},a=e.C("%","$"),b={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",relevance:0},i={b:"fun\\s+"+r+"/\\d+"},l={b:c+"\\(",e:"\\)",rB:!0,relevance:0,c:[{b:c,relevance:0},{b:"\\(",e:"\\)",eW:!0,rE:!0,relevance:0}]},d={b:"{",e:"}",relevance:0},o={b:"\\b_([A-Z][A-Za-z0-9_]*)?",relevance:0},t={b:"[A-Z][a-zA-Z0-9_]*",relevance:0},v={b:"#"+e.UIR,relevance:0,rB:!0,c:[{b:"#"+e.UIR,relevance:0},{b:"{",e:"}",relevance:0}]},f={bK:"fun receive if try case",e:"end",k:n};f.c=[a,i,e.inherit(e.ASM,{cN:""}),f,l,e.QSM,b,d,o,t,v];var s=[a,i,f,l,e.QSM,b,d,o,t,v];l.c[1].c=s,d.c=s;var u={cN:"params",b:"\\(",e:"\\)",c:v.c[1].c=s};return{aliases:["erl"],k:n,i:"(",rB:!0,i:"\\(|#|//|/\\*|\\\\|:|;",c:[u,e.inherit(e.TM,{b:r})],starts:{e:";|\\.",k:n,c:s}},a,{b:"^-",e:"\\.",relevance:0,eE:!0,rB:!0,l:"-"+e.IR,k:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec",c:[u]},b,e.QSM,v,o,t,d,{b:/\.$/}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},a={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,relevance:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],relevance:0},e.HCM,a,{cN:"",b:/\\"/},{cN:"string",b:/'/,e:/'/},t]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("scheme",function(e){var t="[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",r="(\\-|\\+)?\\d+([./]\\d+)?",a={cN:"literal",b:"(#t|#f|#\\\\"+t+"|#\\\\.)"},i={cN:"number",v:[{b:r,relevance:0},{b:"(\\-|\\+)?\\d+([./]\\d+)?[+\\-](\\-|\\+)?\\d+([./]\\d+)?i",relevance:0},{b:"#b[0-1]+(/[0-1]+)?"},{b:"#o[0-7]+(/[0-7]+)?"},{b:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},n=e.QSM,c=[e.C(";","$",{relevance:0}),e.C("#\\|","\\|#")],l={b:t,relevance:0},s={cN:"symbol",b:"'"+t},o={eW:!0,relevance:0},u={v:[{b:/'/},{b:"`"}],c:[{b:"\\(",e:"\\)",c:["self",a,n,i,l,s]}]},p={cN:"name",b:t,l:t,k:{"builtin-name":"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"}},d={v:[{b:"\\(",e:"\\)"},{b:"\\[",e:"\\]"}],c:[{b:/lambda/,eW:!0,rB:!0,c:[p,{b:/\(/,e:/\)/,endsParent:!0,c:[l]}]},p,o]};return o.c=[a,i,n,l,s,u,d].concat(c),{i:/\S/,c:[{cN:"meta",b:"^#!",e:"$"},i,n,s,u,d].concat(c)}});hljs.registerLanguage("basic",function(E){return{cI:!0,i:"^.",l:"[a-zA-Z][a-zA-Z0-9_$%!#]*",k:{keyword:"ABS ASC AND ATN AUTO|0 BEEP BLOAD|10 BSAVE|10 CALL CALLS CDBL CHAIN CHDIR CHR$|10 CINT CIRCLE CLEAR CLOSE CLS COLOR COM COMMON CONT COS CSNG CSRLIN CVD CVI CVS DATA DATE$ DEFDBL DEFINT DEFSNG DEFSTR DEF|0 SEG USR DELETE DIM DRAW EDIT END ENVIRON ENVIRON$ EOF EQV ERASE ERDEV ERDEV$ ERL ERR ERROR EXP FIELD FILES FIX FOR|0 FRE GET GOSUB|10 GOTO HEX$ IF THEN ELSE|0 INKEY$ INP INPUT INPUT# INPUT$ INSTR IMP INT IOCTL IOCTL$ KEY ON OFF LIST KILL LEFT$ LEN LET LINE LLIST LOAD LOC LOCATE LOF LOG LPRINT USING LSET MERGE MID$ MKDIR MKD$ MKI$ MKS$ MOD NAME NEW NEXT NOISE NOT OCT$ ON OR PEN PLAY STRIG OPEN OPTION BASE OUT PAINT PALETTE PCOPY PEEK PMAP POINT POKE POS PRINT PRINT] PSET PRESET PUT RANDOMIZE READ REM RENUM RESET|0 RESTORE RESUME RETURN|0 RIGHT$ RMDIR RND RSET RUN SAVE SCREEN SGN SHELL SIN SOUND SPACE$ SPC SQR STEP STICK STOP STR$ STRING$ SWAP SYSTEM TAB TAN TIME$ TIMER TROFF TRON TO USR VAL VARPTR VARPTR$ VIEW WAIT WHILE WEND WIDTH WINDOW WRITE XOR"},c:[E.QSM,E.C("REM","$",{relevance:10}),E.C("'","$",{relevance:0}),{cN:"symbol",b:"^[0-9]+ ",relevance:10},{cN:"number",b:"\\b([0-9]+[0-9edED.]*[#!]?)",relevance:0},{cN:"number",b:"(&[hH][0-9a-fA-F]{1,4})"},{cN:"number",b:"(&[oO][0-7]{1,6})"}]}});hljs.registerLanguage("swift",function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t=e.C("/\\*","\\*/",{c:["self"]}),n={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},r={cN:"string",c:[e.BE,n],v:[{b:/"""/,e:/"""/},{b:/"/,e:/"/}]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return n.c=[a],{k:i,c:[r,e.CLCM,t,{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},a,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b://},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",a,r,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)"},{bK:"import",e:/$/,c:[e.CLCM,t]}]}});hljs.registerLanguage("ini",function(e){var b={cN:"number",relevance:0,v:[{b:/([\+\-]+)?[\d]+_[\d_]+/},{b:e.NR}]},a=e.C();a.v=[{b:/;/,e:/$/},{b:/#/,e:/$/}];var c={cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"literal",b:/\bon|off|true|false|yes|no\b/},n={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",relevance:10},{b:'"""',e:'"""',relevance:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[a,{cN:"section",b:/\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_\.-]+(?=\s*=\s*)/,cN:"attr",starts:{e:/$/,c:[a,{b:/\[/,e:/\]/,c:[a,r,c,n,b,"self"],relevance:0},r,c,n,b]}}]}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",b={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},r={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},n=[e.C("#","$",{c:[r]}),e.C("^\\=begin","^\\=end",{c:[r],relevance:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:b},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,rB:!0,c:[{b:/<<[-~]?'?/},{b:/\w+/,endSameAsBegin:!0,c:[e.BE,s]}]}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:b},l=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:c}),i].concat(n)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",relevance:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:c}],relevance:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:b},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),relevance:0}].concat(n);s.c=l;var d=[{b:/^\s*=>/,starts:{e:"$",c:i.c=l}},{cN:"meta",b:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:b,i:/\/\*/,c:n.concat(d).concat(l)}});hljs.registerLanguage("plaintext",function(e){return{disableAutodetect:!0}});hljs.registerLanguage("d",function(e){var a="(0|[1-9][\\d_]*)",d="("+a+"|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",t="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",_={cN:"number",b:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},r={cN:"number",b:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},i={cN:"string",b:"'("+t+"|.)",e:"'",i:"."},n={cN:"string",b:'"',c:[{b:t,relevance:0}],e:'"[cwd]?'},c=e.C("\\/\\+","\\+\\/",{c:["self"],relevance:10});return{l:e.UIR,k:{keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},c:[e.CLCM,e.CBCM,c,{cN:"string",b:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},n,{cN:"string",b:'[rq]"',e:'"[cwd]?',relevance:5},{cN:"string",b:"`",e:"`[cwd]?"},{cN:"string",b:'q"\\{',e:'\\}"'},r,_,i,{cN:"meta",b:"^#!",e:"$",relevance:5},{cN:"meta",b:"#(line)",e:"$",relevance:5},{cN:"keyword",b:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}});hljs.registerLanguage("scala",function(e){var t={cN:"subst",v:[{b:"\\$[A-Za-z0-9_]+"},{b:"\\${",e:"}"}]},a={cN:"string",v:[{b:'"',e:'"',i:"\\n",c:[e.BE]},{b:'"""',e:'"""',relevance:10},{b:'[a-z]+"',e:'"',i:"\\n",c:[e.BE,t]},{cN:"string",b:'[a-z]+"""',e:'"""',c:[t],relevance:10}]},c={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},r={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},n={cN:"class",bK:"class object trait type",e:/[:={\[\n;]/,eE:!0,c:[{bK:"extends with",relevance:10},{b:/\[/,e:/\]/,eB:!0,eE:!0,relevance:0,c:[c]},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,relevance:0,c:[c]},r]},l={cN:"function",bK:"def",e:/[:={\[(\n;]/,eE:!0,c:[r]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,a,{cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},c,l,n,e.CNM,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("cs",function(e){var a={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},c={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},r=e.inherit(c,{i:/\n/}),n={cN:"subst",b:"{",e:"}",k:a},t=e.inherit(n,{i:/\n/}),s={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,t]},l={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},n]},b=e.inherit(l,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},t]});n.c=[l,s,c,e.ASM,e.QSM,i,e.CBCM],t.c=[b,s,r,e.ASM,e.QSM,i,e.inherit(e.CBCM,{i:/\n/})];var o={v:[l,s,c,e.ASM,e.QSM]},d=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp","c#"],k:a,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",relevance:0},{b:"\x3c!--|--\x3e"},{b:""}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},o,i,{bK:"class interface",e:/[{;=]/,i:/[^\s:,]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",relevance:0},{cN:"function",b:"("+d+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/\s*[{;=]/,eE:!0,k:a,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],relevance:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:a,relevance:0,c:[o,i,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("cpp",function(e){function t(e){return"(?:"+e+")?"}var r="decltype\\(auto\\)",a="[a-zA-Z_]\\w*::",i=(t(a),t("<.*?>"),{cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"}),c={cN:"string",v:[{b:'(u8?|U|L)?"',e:'"',i:"\\n",c:[e.BE]},{b:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",e:"'",i:"."},{b:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},n={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},c:[{b:/\\\n/,relevance:0},e.inherit(c,{cN:"meta-string"}),{cN:"meta-string",b:/<.*?>/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},o={cN:"title",b:t(a)+e.IR,relevance:0},l=t(a)+e.IR+"\\s*\\(",u={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_tshort reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},p=[i,e.CLCM,e.CBCM,s,c],m={v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:u,c:p.concat([{b:/\(/,e:/\)/,k:u,c:p.concat(["self"]),relevance:0}]),relevance:0},d={cN:"function",b:"((decltype\\(auto\\)|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+"+l,rB:!0,e:/[{;=]/,eE:!0,k:u,i:/[^\w\s\*&:<>]/,c:[{b:r,k:u,relevance:0},{b:l,rB:!0,c:[o],relevance:0},{cN:"params",b:/\(/,e:/\)/,k:u,relevance:0,c:[e.CLCM,e.CBCM,c,s,i,{b:/\(/,e:/\)/,k:u,relevance:0,c:["self",e.CLCM,e.CBCM,c,s,i]}]},i,e.CLCM,e.CBCM,n]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],k:u,i:"",k:u,c:["self",i]},{b:e.IR+"::",k:u},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b://,c:["self"]},e.TM]}]),exports:{preprocessor:n,strings:c,k:u}}});hljs.registerLanguage("less",function(e){function r(e){return{cN:"string",b:"~?"+e+".*?"+e}}function t(e,r,t){return{cN:e,b:r,relevance:t}}var a="[\\w-]+",c="("+a+"|@{"+a+"})",s=[],n=[],b={b:"\\(",e:"\\)",c:n,relevance:0};n.push(e.CLCM,e.CBCM,r("'"),r('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},t("number","#[0-9A-Fa-f]+\\b"),b,t("variable","@@?"+a,10),t("variable","@{"+a+"}"),t("built_in","~?`[^`]*?`"),{cN:"attribute",b:a+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var i=n.concat({b:"{",e:"}",c:s}),l={bK:"when",eW:!0,c:[{bK:"and not"}].concat(n)},o={b:c+"\\s*:",rB:!0,e:"[;}]",relevance:0,c:[{cN:"attribute",b:c,e:":",eE:!0,starts:{eW:!0,i:"[<=$]",relevance:0,c:n}}]},u={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:n,relevance:0}},v={cN:"variable",v:[{b:"@"+a+"\\s*:",relevance:15},{b:"@"+a}],starts:{e:"[;}]",rE:!0,c:i}},C={v:[{b:"[\\.#:&\\[>]",e:"[;{}]"},{b:c,e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",relevance:0,c:[e.CLCM,e.CBCM,l,t("keyword","all\\b"),t("variable","@{"+a+"}"),t("selector-tag",c+"%?",0),t("selector-id","#"+c),t("selector-class","\\."+c,0),t("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"\\(",e:"\\)",c:i},{b:"!important"}]};return s.push(e.CLCM,e.CBCM,u,v,o,C),{cI:!0,i:"[=>'/<($\"]",c:s}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={b:/\{\{/,relevance:0},l={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[e.BE,b],relevance:10},{b:/(u|b)?r?"""/,e:/"""/,c:[e.BE,b],relevance:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[e.BE,b,a,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[e.BE,b,a,c]},{b:/(u|r|ur)'/,e:/'/,relevance:10},{b:/(u|r|ur)"/,e:/"/,relevance:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[e.BE,a,c]},{b:/(fr|rf|f)"/,e:/"/,c:[e.BE,a,c]},e.ASM,e.QSM]},n={cN:"number",relevance:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,n,l,e.HCM]};return c.c=[l,n,b],{aliases:["py","gyp","ipython"],k:r,i:/(<\/|->|\?)|=>/,c:[b,n,{bK:"if",relevance:0},l,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("powershell",function(e){var t={keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter"},n={b:"`[\\s\\S]",relevance:0},c={cN:"variable",v:[{b:/\$\B/},{cN:"keyword",b:/\$this/},{b:/\$[\w\d][\w\d_:]*/}]},i={cN:"string",v:[{b:/"/,e:/"/},{b:/@"/,e:/^"@/}],c:[n,c,{cN:"variable",b:/\$[A-z]/,e:/[^A-z]/}]},a={cN:"string",v:[{b:/'/,e:/'/},{b:/@'/,e:/^'@/}]},r=e.inherit(e.C(null,null),{v:[{b:/#/,e:/$/},{b:/<#/,e:/#>/}],c:[{cN:"doctag",v:[{b:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{b:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),o={cN:"built_in",v:[{b:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|New|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Complete|Confirm|Deny|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},l={cN:"class",bK:"class enum",e:/\s*[{]/,eE:!0,relevance:0,c:[e.TM]},s={cN:"function",b:/function\s+/,e:/\s*\{|$/,eE:!0,rB:!0,relevance:0,c:[{b:"function",relevance:0,cN:"keyword"},{cN:"title",b:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{b:/\(/,e:/\)/,cN:"params",relevance:0,c:[c]}]},p={b:/using\s/,e:/$/,rB:!0,c:[i,a,{cN:"keyword",b:/(using|assembly|command|module|namespace|type)/}]},b={v:[{cN:"operator",b:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{cN:"literal",b:/(-)[\w\d]+/,relevance:0}]},d={cN:"function",b:/\[.*\]\s*[\w]+[ ]??\(/,e:/$/,rB:!0,relevance:0,c:[{cN:"keyword",b:"(".concat(t.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},e.inherit(e.TM,{endsParent:!0})]},u=[d,r,n,e.NM,i,a,o,c,{cN:"literal",b:/\$(null|true|false)\b/},{cN:"selector-tag",b:/\@\B/,relevance:0}],m={b:/\[/,e:/\]/,eB:!0,eE:!0,relevance:0,c:[].concat("self",u,{b:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",cN:"built_in",relevance:0},{cN:"type",b:/[\.\w\d]+/,relevance:0})};return d.c.unshift(m),{aliases:["ps","ps1"],l:/-?[A-z\.\-]+/,cI:!0,k:t,c:u.concat(l,s,p,b,m)}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",relevance:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]{0,3}(?=\\W)",relevance:0},{b:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",a={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,a]},{b:/[:\(,=]\s*/,relevance:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[a]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,relevance:0}])}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.CLCM,e.CBCM],c=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:c,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})].concat(n),i:"\\S"},a={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return c.push(t,a),n.forEach(function(e){c.push(e)}),{c:c,k:i,i:"\\S"}});hljs.registerLanguage("xml",function(e){var c={cN:"symbol",b:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},s={b:"\\s",c:[{cN:"meta-keyword",b:"#?[a-z_][a-z1-9_-]+",i:"\\n"}]},a=e.inherit(s,{b:"\\(",e:"\\)"}),t=e.inherit(e.ASM,{cN:"meta-string"}),l=e.inherit(e.QSM,{cN:"meta-string"}),r={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],cI:!0,c:[{cN:"meta",b:"",relevance:10,c:[s,l,t,a,{b:"\\[",e:"\\]",c:[{cN:"meta",b:"",c:[s,a,l,t]}]}]},e.C("\x3c!--","--\x3e",{relevance:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",relevance:10},c,{cN:"meta",b:/<\?xml/,e:/\?>/,relevance:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},e.inherit(e.ASM,{i:null,cN:null,c:null,skip:!0}),e.inherit(e.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:")",e:">",k:{name:"style"},c:[r],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:")",e:">",k:{name:"script"},c:[r],starts:{e:"<\/script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,relevance:0},r]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",relevance:0},{cN:"bullet",b:"^\\s*([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",relevance:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```\\w*\\s*$",e:"^```[ ]*$"},{b:"`.+?`"},{b:"^( {4}|\\t)",e:"$",relevance:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,relevance:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],relevance:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},c:[{cN:"string",b:"'",e:"'",c:[{b:"''"}]},{cN:"string",b:'"',e:'"',c:[{b:'""'}]},{cN:"string",b:"`",e:"`"},e.CNM,e.CBCM,t,e.HCM]},e.CBCM,t,e.HCM]}});hljs.registerLanguage("javascript",function(e){var r="<>",a="",t={b:/<[A-Za-z0-9\\._:-]+/,e:/\/[A-Za-z0-9\\._:-]+>|\/>/},c="[A-Za-z$_][0-9A-Za-z$_]*",n={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:n,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},b={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},l={cN:"string",b:"`",e:"`",c:[e.BE,o]};o.c=[e.ASM,e.QSM,i,b,l,s,e.RM];var u=o.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx","mjs","cjs"],k:n,c:[{cN:"meta",relevance:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,i,b,l,e.CLCM,e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+",c:[{cN:"type",b:"\\{",e:"\\}",relevance:0},{cN:"variable",b:c+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{b:/(?=[^\n])\s/,relevance:0}]}]}),e.CBCM,s,{b:/[{,\n]\s*/,relevance:0,c:[{b:c+"\\s*:",rB:!0,relevance:0,c:[{cN:"attr",b:c,relevance:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+c+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:c},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:n,c:u}]}]},{cN:"",b:/\s/,e:/\s*/,skip:!0},{v:[{b:r,e:a},{b:t.b,e:t.e}],sL:"xml",c:[{b:t.b,e:t.e,skip:!0,c:["self"]}]}],relevance:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:c}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:u}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor get set",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,relevance:0}]},c=[e.BE,r,n],a=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:c,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",relevance:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",relevance:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",relevance:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",relevance:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",relevance:5},{b:"qw\\s+q",e:"q",relevance:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],relevance:0},{b:"-?\\w+\\s*\\=\\>",c:[],relevance:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",relevance:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],relevance:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,relevance:5,c:[e.TM]},{b:"-\\w\\b",relevance:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=a,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:s.c=a}});hljs.registerLanguage("tex",function(e){var c={cN:"tag",b:/\\/,relevance:0,c:[{cN:"name",v:[{b:/[a-zA-Z\u0430-\u044f\u0410-\u042f]+[*]?/},{b:/[^a-zA-Z\u0430-\u044f\u0410-\u042f0-9]/}],starts:{eW:!0,relevance:0,c:[{cN:"string",v:[{b:/\[/,e:/\]/},{b:/\{/,e:/\}/}]},{b:/\s*=\s*/,eW:!0,relevance:0,c:[{cN:"number",b:/-?\d*\.?\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?/}]}]}}]};return{c:[c,{cN:"formula",c:[c],relevance:0,v:[{b:/\$\$/,e:/\$\$/},{b:/\$/,e:/\$/}]},e.C("%","$",{relevance:0})]}});hljs.registerLanguage("fortran",function(e){return{cI:!0,aliases:["f90","f95"],k:{literal:".False. .True.",keyword:"kind do while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then block endblock public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure integer real character complex logical dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data",built_in:"alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_ofacosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image"},i:/\/\*/,c:[e.inherit(e.ASM,{cN:"string",relevance:0}),e.inherit(e.QSM,{cN:"string",relevance:0}),{cN:"function",bK:"subroutine function program",i:"[${=\\n]",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)"}]},e.C("!","$",{relevance:0}),{cN:"number",b:"(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?",relevance:0}]}});hljs.registerLanguage("protobuf",function(e){return{k:{keyword:"package import option optional required repeated group oneof",built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",literal:"true false"},c:[e.QSM,e.NM,e.CLCM,{cN:"class",bK:"message enum service",e:/\{/,i:/\n/,c:[e.inherit(e.TM,{starts:{eW:!0,eE:!0}})]},{cN:"function",bK:"rpc",e:/;/,eE:!0,k:"rpc returns"},{b:/^\s*[A-Z_]+/,e:/\s*=/,eE:!0}]}});hljs.registerLanguage("java",function(e){var a="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",t={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0};return{aliases:["jsp"],k:a,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{b:/\w+@/,relevance:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",relevance:0},{cN:"function",b:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:a,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:a,relevance:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},t,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("properties",function(e){var r="[ \\t\\f]*",t="("+r+"[:=]"+r+"|[ \\t\\f]+)",n="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",c={e:t,relevance:0,starts:{cN:"string",e:/$/,relevance:0,c:[{b:"\\\\\\n"}]}};return{cI:!0,i:/\S/,c:[e.C("^\\s*[!#]","$"),{b:n+t,rB:!0,c:[{cN:"attr",b:n,endsParent:!0,relevance:0}],starts:c},{b:a+t,rB:!0,relevance:0,c:[{cN:"meta",b:a,endsParent:!0,relevance:0}],starts:c},{cN:"attr",relevance:0,b:a+r+"$"}]}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php","php3","php4","php5","php6","php7"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],relevance:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],relevance:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%/,e:/$/,i:"\\n"},e.CLCM,e.CBCM]},{cN:"class",b:"("+i.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:i,l:t,c:[e.UTM]},{b:"\\."+e.UIR,relevance:0}]}});hljs.registerLanguage("typescript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"},n={cN:"meta",b:"@"+r},a={b:"\\(",e:/\)/,k:t,c:["self",e.QSM,e.ASM,e.NM]},c={cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:[e.CLCM,e.CBCM,n,a]},s={cN:"number",v:[{b:"\\b(0[bB][01]+)n?"},{b:"\\b(0[oO][0-7]+)n?"},{b:e.CNR+"n?"}],relevance:0},o={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},i={b:"html`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"xml"}},l={b:"css`",e:"",starts:{e:"`",rE:!1,c:[e.BE,o],sL:"css"}},b={cN:"string",b:"`",e:"`",c:[e.BE,o]};return o.c=[e.ASM,e.QSM,i,l,b,s,e.RM],{aliases:["ts"],k:t,c:[{cN:"meta",b:/^\s*['"]use strict['"]/},e.ASM,e.QSM,i,l,b,e.CLCM,e.CBCM,s,{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+e.IR+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:e.IR},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:["self",e.CLCM,e.CBCM]}]}]}],relevance:0},{cN:"function",bK:"function",e:/[\{;]/,eE:!0,k:t,c:["self",e.inherit(e.TM,{b:r}),c],i:/%/,relevance:0},{bK:"constructor",e:/[\{;]/,eE:!0,c:["self",c]},{b:/module\./,k:{built_in:"module"},relevance:0},{bK:"module",e:/\{/,eE:!0},{bK:"interface",e:/\{/,eE:!0,k:"interface extends"},{b:/\$[(.]/},{b:"\\."+e.IR,relevance:0},n,a]}});hljs.registerLanguage("elm",function(e){var i={v:[e.C("--","$"),e.C("{-","-}",{c:["self"]})]},t={cN:"type",b:"\\b[A-Z][\\w']*",relevance:0},c={b:"\\(",e:"\\)",i:'"',c:[{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},i]};return{k:"let in if then else case of where module import exposing type alias as infix infixl infixr port effect command subscription",c:[{bK:"port effect module",e:"exposing",k:"port effect module where command subscription exposing",c:[c,i],i:"\\W\\.|;"},{b:"import",e:"$",k:"import as exposing",c:[c,i],i:"\\W\\.|;"},{b:"type",e:"$",k:"type alias",c:[t,c,{b:"{",e:"}",c:c.c},i]},{bK:"infix infixl infixr",e:"$",c:[e.CNM,i]},{b:"port",e:"$",k:"port",c:[i]},{cN:"string",b:"'\\\\?.",e:"'",i:"."},e.QSM,e.CNM,t,e.inherit(e.TM,{b:"^[_a-z][\\w']*"}),i,{b:"->|<-"}],i:/;/}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",relevance:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",relevance:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/^\*{15}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("lisp",function(e){var b="[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*",c="\\|[^]*?\\|",a="(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",l={cN:"literal",b:"\\b(t{1}|nil)\\b"},n={cN:"number",v:[{b:a,relevance:0},{b:"#(b|B)[0-1]+(/[0-1]+)?"},{b:"#(o|O)[0-7]+(/[0-7]+)?"},{b:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},{b:"#(c|C)\\("+a+" +"+a,e:"\\)"}]},r=e.inherit(e.QSM,{i:null}),i=e.C(";","$",{relevance:0}),t={b:"\\*",e:"\\*"},v={cN:"symbol",b:"[:&]"+b},s={b:b,relevance:0},u={b:c},d={c:[n,r,t,v,{b:"\\(",e:"\\)",c:["self",l,r,n,s]},s],v:[{b:"['`]\\(",e:"\\)"},{b:"\\(quote ",e:"\\)",k:{name:"quote"}},{b:"'"+c}]},f={v:[{b:"'"+b},{b:"#'"+b+"(::"+b+")*"}]},m={b:"\\(\\s*",e:"\\)"},o={eW:!0,relevance:0};return m.c=[{cN:"name",v:[{b:b},{b:c}]},o],o.c=[d,f,m,l,n,r,i,t,v,u,s],{i:/\S/,c:[n,{cN:"meta",b:"^#!",e:"$"},l,r,i,d,f,m,s]}});hljs.registerLanguage("kotlin",function(e){var t={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={cN:"symbol",b:e.UIR+"@"},n={cN:"subst",b:"\\${",e:"}",c:[e.CNM]},c={cN:"variable",b:"\\$"+e.UIR},r={cN:"string",v:[{b:'"""',e:'"""(?=[^"])',c:[c,n]},{b:"'",e:"'",i:/\n/,c:[e.BE]},{b:'"',e:'"',i:/\n/,c:[e.BE,c,n]}]};n.c.push(r);var i={cN:"meta",b:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UIR+")?"},l={cN:"meta",b:"@"+e.UIR,c:[{b:/\(/,e:/\)/,c:[e.inherit(r,{cN:"meta-string"})]}]},s={cN:"number",b:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},b=e.C("/\\*","\\*/",{c:[e.CBCM]}),o={v:[{cN:"type",b:e.UIR},{b:/\(/,e:/\)/,c:[]}]},d=o;return d.v[1].c=[o],o.v[1].c=[d],{aliases:["kt"],k:t,c:[e.C("/\\*\\*","\\*/",{relevance:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,b,{cN:"keyword",b:/\b(break|continue|return|this)\b/,starts:{c:[{cN:"symbol",b:/@\w+/}]}},a,i,l,{cN:"function",bK:"fun",e:"[(]|$",rB:!0,eE:!0,k:t,i:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,c:[{b:e.UIR+"\\s*\\(",rB:!0,relevance:0,c:[e.UTM]},{cN:"type",b://,k:"reified",relevance:0},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:t,relevance:0,c:[{b:/:/,e:/[=,\/]/,eW:!0,c:[o,e.CLCM,b],relevance:0},e.CLCM,b,i,l,r,e.CNM]},b]},{cN:"class",bK:"class interface trait",e:/[:\{(]|$/,eE:!0,i:"extends implements",c:[{bK:"public protected internal private constructor"},e.UTM,{cN:"type",b://,eB:!0,eE:!0,relevance:0},{cN:"type",b:/[,:]\s*/,e:/[<\(,]|$/,eB:!0,rE:!0},i,l]},r,{cN:"meta",b:"^#!/usr/bin/env",e:"$",i:"\n"},s]}});hljs.registerLanguage("dart",function(e){var t={cN:"subst",v:[{b:"\\$[A-Za-z0-9_]+"}]},r={cN:"subst",v:[{b:"\\${",e:"}"}],k:"true false null this is new super"},n={cN:"string",v:[{b:"r'''",e:"'''"},{b:'r"""',e:'"""'},{b:"r'",e:"'",i:"\\n"},{b:'r"',e:'"',i:"\\n"},{b:"'''",e:"'''",c:[e.BE,t,r]},{b:'"""',e:'"""',c:[e.BE,t,r]},{b:"'",e:"'",i:"\\n",c:[e.BE,t,r]},{b:'"',e:'"',i:"\\n",c:[e.BE,t,r]}]};r.c=[e.CNM,n];return{k:{keyword:"abstract as assert async await break case catch class const continue covariant default deferred do dynamic else enum export extends extension external factory false final finally for Function get hide if implements import in inferface is library mixin new null on operator part rethrow return set show static super switch sync this throw true try typedef var void while with yield",built_in:"Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double dynamic int num print Element ElementList document querySelector querySelectorAll window"},c:[n,e.C("/\\*\\*","\\*/",{sL:"markdown"}),e.C("///+\\s*","$",{c:[{sL:"markdown",b:".",e:"$"}]}),e.CLCM,e.CBCM,{cN:"class",bK:"class interface",e:"{",eE:!0,c:[{bK:"extends implements"},e.UTM]},e.CNM,{cN:"meta",b:"@[A-Za-z]+"},{b:"=>"}]}});hljs.registerLanguage("applescript",function(e){var t=e.inherit(e.QSM,{i:""}),r={cN:"params",b:"\\(",e:"\\)",c:["self",e.CNM,t]},i=e.C("--","$"),o=[i,e.C("\\(\\*","\\*\\)",{c:["self",i]}),e.HCM];return{aliases:["osascript"],k:{keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",literal:"AppleScript false linefeed return pi quote result space tab true",built_in:"alias application boolean class constant date file integer list number real record string text activate beep count delay launch log offset read round run say summarize write character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"},c:[t,e.CNM,{cN:"built_in",b:"\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b"},{cN:"literal",b:"\\b(text item delimiters|current application|missing value)\\b"},{cN:"keyword",b:"\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference)|POSIX file|POSIX path|(date|time) string|quoted form)\\b"},{bK:"on",i:"[${=;\\n]",c:[e.UTM,r]}].concat(o),i:"//|->|=>|\\[\\["}});hljs.registerLanguage("autohotkey",function(e){var a={b:"`[\\s\\S]"};return{cI:!0,aliases:["ahk"],k:{keyword:"Break Continue Critical Exit ExitApp Gosub Goto New OnExit Pause return SetBatchLines SetTimer Suspend Thread Throw Until ahk_id ahk_class ahk_pid ahk_exe ahk_group",literal:"true false NOT AND OR",built_in:"ComSpec Clipboard ClipboardAll ErrorLevel"},c:[a,e.inherit(e.QSM,{c:[a]}),e.C(";","$",{relevance:0}),e.CBCM,{cN:"number",b:e.NR,relevance:0},{cN:"variable",b:"%[a-zA-Z0-9#_$@]+%"},{cN:"built_in",b:"^\\s*\\w+\\s*(,|%)"},{cN:"title",v:[{b:'^[^\\n";]+::(?!=)'},{b:'^[^\\n";]+:(?!=)',relevance:0}]},{cN:"meta",b:"^\\s*#\\w+",e:"$",relevance:0},{cN:"built_in",b:"A_[a-zA-Z0-9]+"},{b:",\\s*,"}]}});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:r},l:e.IR+"!?",i:""}]}});hljs.registerLanguage("scss",function(e){var t="@[a-z-]+",r={cN:"variable",b:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={cN:"number",b:"#[0-9A-Fa-f]+"};e.CSSNM,e.QSM,e.ASM,e.CBCM;return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",relevance:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",relevance:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{cN:"selector-pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"selector-pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},r,{cN:"attribute",b:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[r,i,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@(page|font-face)",l:t,k:"@page @font-face"},{b:"@",e:"[{;]",rB:!0,k:"and or not only",c:[{b:t,cN:"keyword"},r,e.QSM,e.ASM,i,e.CSSNM]}]}});hljs.registerLanguage("dockerfile",function(e){return{aliases:["docker"],cI:!0,k:"from maintainer expose env arg user onbuild stopsignal",c:[e.HCM,e.ASM,e.QSM,e.NM,{bK:"run cmd entrypoint volume add copy workdir label healthcheck shell",starts:{e:/[^\\]$/,sL:"bash"}}],i:""},{cN:"attribute",b:/\w+/,relevance:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,relevance:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("prolog",function(e){var c={b:/\(/,e:/\)/,relevance:0},b={b:/\[/,e:/\]/},n={cN:"comment",b:/%/,e:/$/,c:[e.PWM]},r={cN:"string",b:/`/,e:/`/,c:[e.BE]},a=[{b:/[a-z][A-Za-z0-9_]*/,relevance:0},{cN:"symbol",v:[{b:/[A-Z][a-zA-Z0-9_]*/},{b:/_[A-Za-z0-9_]*/}],relevance:0},c,{b:/:-/},b,n,e.CBCM,e.QSM,e.ASM,r,{cN:"string",b:/0\'(\\\'|.)/},{cN:"string",b:/0\'\\s/},e.CNM];return c.c=a,{c:(b.c=a).concat([{b:/\.$/}])}});hljs.registerLanguage("matlab",function(e){var a="('|\\.')+",s={relevance:0,c:[{b:a}]};return{k:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson max min nanmax nanmin mean nanmean type table readtable writetable sortrows sort figure plot plot3 scatter scatter3 cellfun legend intersect ismember procrustes hold num2cell "},i:'(//|"|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function",e:"$",c:[e.UTM,{cN:"params",v:[{b:"\\(",e:"\\)"},{b:"\\[",e:"\\]"}]}]},{cN:"built_in",b:/true|false/,relevance:0,starts:s},{b:"[a-zA-Z][a-zA-Z_0-9]*"+a,relevance:0},{cN:"number",b:e.CNR,relevance:0,starts:s},{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{b:/\]|}|\)/,relevance:0,starts:s},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}],starts:s},e.C("^\\s*\\%\\{\\s*$","^\\s*\\%\\}\\s*$"),e.C("\\%","$")]}});hljs.registerLanguage("tcl",function(e){return{aliases:["tk"],k:"after append apply array auto_execok auto_import auto_load auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock close concat continue dde dict encoding eof error eval exec exit expr fblocked fconfigure fcopy file fileevent filename flush for foreach format gets glob global history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename return safe scan seek set socket source split string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update uplevel upvar variable vwait while",c:[e.C(";[ \\t]*#","$"),e.C("^[ \\t]*#","$"),{bK:"proc",e:"[\\{]",eE:!0,c:[{cN:"title",b:"[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"[ \\t\\n\\r]",eW:!0,eE:!0}]},{eE:!0,v:[{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*\\(([a-zA-Z0-9_])*\\)",e:"[^a-zA-Z0-9_\\}\\$]"},{b:"\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*",e:"(\\))?[^a-zA-Z0-9_\\}\\$]"}]},{cN:"string",c:[e.BE],v:[e.inherit(e.QSM,{i:null})]},{cN:"number",v:[e.BNM,e.CNM]}]}});hljs.registerLanguage("go",function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:n,i:"]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,relevance:0},{cN:"type",b:"!"+e.UIR},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"\\-(?=[ ]|$)",relevance:0},e.HCM,{bK:b,k:{literal:b}},{cN:"number",b:e.CNR+"\\b"},a]}}); \ No newline at end of file +// @license-start magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause/*! highlight.js v9.18.1 | BSD3 License | git.io/hljslicense */ +!function (e) { var n = "object" == typeof window && window || "object" == typeof self && self; "undefined" == typeof exports || exports.nodeType ? n && (n.hljs = e({}), "function" == typeof define && define.amd && define([], function () { return n.hljs })) : e(exports) }(function (a) { var f = [], o = Object.keys, _ = {}, g = {}, C = !0, n = /^(no-?highlight|plain|text)$/i, E = /\blang(?:uage)?-([\w-]+)\b/i, t = /((^(<[^>]+>|\t|)+|(?:\n)))/gm, r = { case_insensitive: "cI", lexemes: "l", contains: "c", keywords: "k", subLanguage: "sL", className: "cN", begin: "b", beginKeywords: "bK", end: "e", endsWithParent: "eW", illegal: "i", excludeBegin: "eB", excludeEnd: "eE", returnBegin: "rB", returnEnd: "rE", variants: "v", IDENT_RE: "IR", UNDERSCORE_IDENT_RE: "UIR", NUMBER_RE: "NR", C_NUMBER_RE: "CNR", BINARY_NUMBER_RE: "BNR", RE_STARTERS_RE: "RSR", BACKSLASH_ESCAPE: "BE", APOS_STRING_MODE: "ASM", QUOTE_STRING_MODE: "QSM", PHRASAL_WORDS_MODE: "PWM", C_LINE_COMMENT_MODE: "CLCM", C_BLOCK_COMMENT_MODE: "CBCM", HASH_COMMENT_MODE: "HCM", NUMBER_MODE: "NM", C_NUMBER_MODE: "CNM", BINARY_NUMBER_MODE: "BNM", CSS_NUMBER_MODE: "CSSNM", REGEXP_MODE: "RM", TITLE_MODE: "TM", UNDERSCORE_TITLE_MODE: "UTM", COMMENT: "C", beginRe: "bR", endRe: "eR", illegalRe: "iR", lexemesRe: "lR", terminators: "t", terminator_end: "tE" }, m = "", O = "Could not find the language '{}', did you forget to load/include a language module?", B = { classPrefix: "hljs-", tabReplace: null, useBR: !1, languages: void 0 }, c = "of and for in not or if then".split(" "); function x(e) { return e.replace(/&/g, "&").replace(//g, ">") } function d(e) { return e.nodeName.toLowerCase() } function R(e) { return n.test(e) } function i(e) { var n, t = {}, r = Array.prototype.slice.call(arguments, 1); for (n in e) t[n] = e[n]; return r.forEach(function (e) { for (n in e) t[n] = e[n] }), t } function p(e) { var a = []; return function e(n, t) { for (var r = n.firstChild; r; r = r.nextSibling)3 === r.nodeType ? t += r.nodeValue.length : 1 === r.nodeType && (a.push({ event: "start", offset: t, node: r }), t = e(r, t), d(r).match(/br|hr|img|input/) || a.push({ event: "stop", offset: t, node: r })); return t }(e, 0), a } function v(e, n, t) { var r = 0, a = "", i = []; function o() { return e.length && n.length ? e[0].offset !== n[0].offset ? e[0].offset < n[0].offset ? e : n : "start" === n[0].event ? e : n : e.length ? e : n } function c(e) { a += "<" + d(e) + f.map.call(e.attributes, function (e) { return " " + e.nodeName + '="' + x(e.value).replace(/"/g, """) + '"' }).join("") + ">" } function l(e) { a += "" } function u(e) { ("start" === e.event ? c : l)(e.node) } for (; e.length || n.length;) { var s = o(); if (a += x(t.substring(r, s[0].offset)), r = s[0].offset, s === e) { for (i.reverse().forEach(l); u(s.splice(0, 1)[0]), (s = o()) === e && s.length && s[0].offset === r;); i.reverse().forEach(c) } else "start" === s[0].event ? i.push(s[0].node) : i.pop(), u(s.splice(0, 1)[0]) } return a + x(t.substr(r)) } function l(n) { return n.v && !n.cached_variants && (n.cached_variants = n.v.map(function (e) { return i(n, { v: null }, e) })), n.cached_variants ? n.cached_variants : function e(n) { return !!n && (n.eW || e(n.starts)) }(n) ? [i(n, { starts: n.starts ? i(n.starts) : null })] : Object.isFrozen(n) ? [i(n)] : [n] } function u(e) { if (r && !e.langApiRestored) { for (var n in e.langApiRestored = !0, r) e[n] && (e[r[n]] = e[n]); (e.c || []).concat(e.v || []).forEach(u) } } function M(n, t) { var i = {}; return "string" == typeof n ? r("keyword", n) : o(n).forEach(function (e) { r(e, n[e]) }), i; function r(a, e) { t && (e = e.toLowerCase()), e.split(" ").forEach(function (e) { var n, t, r = e.split("|"); i[r[0]] = [a, (n = r[0], (t = r[1]) ? Number(t) : function (e) { return -1 != c.indexOf(e.toLowerCase()) }(n) ? 0 : 1)] }) } } function S(r) { function s(e) { return e && e.source || e } function f(e, n) { return new RegExp(s(e), "m" + (r.cI ? "i" : "") + (n ? "g" : "")) } function a(a) { var i, e, o = {}, c = [], l = {}, t = 1; function n(e, n) { o[t] = e, c.push([e, n]), t += new RegExp(n.toString() + "|").exec("").length - 1 + 1 } for (var r = 0; r < a.c.length; r++) { n(e = a.c[r], e.bK ? "\\.?(?:" + e.b + ")\\.?" : e.b) } a.tE && n("end", a.tE), a.i && n("illegal", a.i); var u = c.map(function (e) { return e[1] }); return i = f(function (e, n) { for (var t = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./, r = 0, a = "", i = 0; i < e.length; i++) { var o = r += 1, c = s(e[i]); for (0 < i && (a += n), a += "("; 0 < c.length;) { var l = t.exec(c); if (null == l) { a += c; break } a += c.substring(0, l.index), c = c.substring(l.index + l[0].length), "\\" == l[0][0] && l[1] ? a += "\\" + String(Number(l[1]) + o) : (a += l[0], "(" == l[0] && r++) } a += ")" } return a }(u, "|"), !0), l.lastIndex = 0, l.exec = function (e) { var n; if (0 === c.length) return null; i.lastIndex = l.lastIndex; var t = i.exec(e); if (!t) return null; for (var r = 0; r < t.length; r++)if (null != t[r] && null != o["" + r]) { n = o["" + r]; break } return "string" == typeof n ? (t.type = n, t.extra = [a.i, a.tE]) : (t.type = "begin", t.rule = n), t }, l } if (r.c && -1 != r.c.indexOf("self")) { if (!C) throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation."); r.c = r.c.filter(function (e) { return "self" != e }) } !function n(t, e) { t.compiled || (t.compiled = !0, t.k = t.k || t.bK, t.k && (t.k = M(t.k, r.cI)), t.lR = f(t.l || /\w+/, !0), e && (t.bK && (t.b = "\\b(" + t.bK.split(" ").join("|") + ")\\b"), t.b || (t.b = /\B|\b/), t.bR = f(t.b), t.endSameAsBegin && (t.e = t.b), t.e || t.eW || (t.e = /\B|\b/), t.e && (t.eR = f(t.e)), t.tE = s(t.e) || "", t.eW && e.tE && (t.tE += (t.e ? "|" : "") + e.tE)), t.i && (t.iR = f(t.i)), null == t.relevance && (t.relevance = 1), t.c || (t.c = []), t.c = Array.prototype.concat.apply([], t.c.map(function (e) { return l("self" === e ? t : e) })), t.c.forEach(function (e) { n(e, t) }), t.starts && n(t.starts, e), t.t = a(t)) }(r) } function T(n, e, a, t) { var i = e; function c(e, n, t, r) { if (!t && "" === n) return ""; if (!e) return n; var a = '') + n + (t ? "" : m) } function o() { p += (null != d.sL ? function () { var e = "string" == typeof d.sL; if (e && !_[d.sL]) return x(v); var n = e ? T(d.sL, v, !0, R[d.sL]) : w(v, d.sL.length ? d.sL : void 0); return 0 < d.relevance && (M += n.relevance), e && (R[d.sL] = n.top), c(n.language, n.value, !1, !0) } : function () { var e, n, t, r, a, i, o; if (!d.k) return x(v); for (r = "", n = 0, d.lR.lastIndex = 0, t = d.lR.exec(v); t;)r += x(v.substring(n, t.index)), a = d, i = t, o = g.cI ? i[0].toLowerCase() : i[0], (e = a.k.hasOwnProperty(o) && a.k[o]) ? (M += e[1], r += c(e[0], x(t[0]))) : r += x(t[0]), n = d.lR.lastIndex, t = d.lR.exec(v); return r + x(v.substr(n)) })(), v = "" } function l(e) { p += e.cN ? c(e.cN, "", !0) : "", d = Object.create(e, { parent: { value: d } }) } function u(e) { var n = e[0], t = e.rule; return t && t.endSameAsBegin && (t.eR = new RegExp(n.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), "m")), t.skip ? v += n : (t.eB && (v += n), o(), t.rB || t.eB || (v = n)), l(t), t.rB ? 0 : n.length } function s(e) { var n = e[0], t = i.substr(e.index), r = function e(n, t) { if (r = n.eR, a = t, (i = r && r.exec(a)) && 0 === i.index) { for (; n.endsParent && n.parent;)n = n.parent; return n } var r, a, i; if (n.eW) return e(n.parent, t) }(d, t); if (r) { var a = d; for (a.skip ? v += n : (a.rE || a.eE || (v += n), o(), a.eE && (v = n)); d.cN && (p += m), d.skip || d.sL || (M += d.relevance), (d = d.parent) !== r.parent;); return r.starts && (r.endSameAsBegin && (r.starts.eR = r.eR), l(r.starts)), a.rE ? 0 : n.length } } var f = {}; function r(e, n) { var t = n && n[0]; if (v += e, null == t) return o(), 0; if ("begin" == f.type && "end" == n.type && f.index == n.index && "" === t) return v += i.slice(n.index, n.index + 1), 1; if ("begin" === (f = n).type) return u(n); if ("illegal" === n.type && !a) throw new Error('Illegal lexeme "' + t + '" for mode "' + (d.cN || "") + '"'); if ("end" === n.type) { var r = s(n); if (null != r) return r } return v += t, t.length } var g = D(n); if (!g) throw console.error(O.replace("{}", n)), new Error('Unknown language: "' + n + '"'); S(g); var E, d = t || g, R = {}, p = ""; for (E = d; E !== g; E = E.parent)E.cN && (p = c(E.cN, "", !0) + p); var v = "", M = 0; try { for (var b, h, N = 0; d.t.lastIndex = N, b = d.t.exec(i);)h = r(i.substring(N, b.index), b), N = b.index + h; for (r(i.substr(N)), E = d; E.parent; E = E.parent)E.cN && (p += m); return { relevance: M, value: p, i: !1, language: n, top: d } } catch (e) { if (e.message && -1 !== e.message.indexOf("Illegal")) return { i: !0, relevance: 0, value: x(i) }; if (C) return { relevance: 0, value: x(i), language: n, top: d, errorRaised: e }; throw e } } function w(t, e) { e = e || B.languages || o(_); var r = { relevance: 0, value: x(t) }, a = r; return e.filter(D).filter(L).forEach(function (e) { var n = T(e, t, !1); n.language = e, n.relevance > a.relevance && (a = n), n.relevance > r.relevance && (a = r, r = n) }), a.language && (r.second_best = a), r } function b(e) { return B.tabReplace || B.useBR ? e.replace(t, function (e, n) { return B.useBR && "\n" === e ? "
" : B.tabReplace ? n.replace(/\t/g, B.tabReplace) : "" }) : e } function s(e) { var n, t, r, a, i, o, c, l, u, s, f = function (e) { var n, t, r, a, i = e.className + " "; if (i += e.parentNode ? e.parentNode.className : "", t = E.exec(i)) { var o = D(t[1]); return o || (console.warn(O.replace("{}", t[1])), console.warn("Falling back to no-highlight mode for this block.", e)), o ? t[1] : "no-highlight" } for (n = 0, r = (i = i.split(/\s+/)).length; n < r; n++)if (R(a = i[n]) || D(a)) return a }(e); R(f) || (B.useBR ? (n = document.createElement("div")).innerHTML = e.innerHTML.replace(/\n/g, "").replace(//g, "\n") : n = e, i = n.textContent, r = f ? T(f, i, !0) : w(i), (t = p(n)).length && ((a = document.createElement("div")).innerHTML = r.value, r.value = v(t, p(a), i)), r.value = b(r.value), e.innerHTML = r.value, e.className = (o = e.className, c = f, l = r.language, u = c ? g[c] : l, s = [o.trim()], o.match(/\bhljs\b/) || s.push("hljs"), -1 === o.indexOf(u) && s.push(u), s.join(" ").trim()), e.result = { language: r.language, re: r.relevance }, r.second_best && (e.second_best = { language: r.second_best.language, re: r.second_best.relevance })) } function h() { if (!h.called) { h.called = !0; var e = document.querySelectorAll("pre code"); f.forEach.call(e, s) } } var N = { disableAutodetect: !0 }; function D(e) { return e = (e || "").toLowerCase(), _[e] || _[g[e]] } function L(e) { var n = D(e); return n && !n.disableAutodetect } return a.highlight = T, a.highlightAuto = w, a.fixMarkup = b, a.highlightBlock = s, a.configure = function (e) { B = i(B, e) }, a.initHighlighting = h, a.initHighlightingOnLoad = function () { window.addEventListener("DOMContentLoaded", h, !1), window.addEventListener("load", h, !1) }, a.registerLanguage = function (n, e) { var t; try { t = e(a) } catch (e) { if (console.error("Language definition for '{}' could not be registered.".replace("{}", n)), !C) throw e; console.error(e), t = N } u(_[n] = t), t.rawDefinition = e.bind(null, a), t.aliases && t.aliases.forEach(function (e) { g[e] = n }) }, a.listLanguages = function () { return o(_) }, a.getLanguage = D, a.requireLanguage = function (e) { var n = D(e); if (n) return n; throw new Error("The '{}' language is required, but not loaded.".replace("{}", e)) }, a.autoDetection = L, a.inherit = i, a.debugMode = function () { C = !1 }, a.IR = a.IDENT_RE = "[a-zA-Z]\\w*", a.UIR = a.UNDERSCORE_IDENT_RE = "[a-zA-Z_]\\w*", a.NR = a.NUMBER_RE = "\\b\\d+(\\.\\d+)?", a.CNR = a.C_NUMBER_RE = "(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)", a.BNR = a.BINARY_NUMBER_RE = "\\b(0b[01]+)", a.RSR = a.RE_STARTERS_RE = "!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", a.BE = a.BACKSLASH_ESCAPE = { b: "\\\\[\\s\\S]", relevance: 0 }, a.ASM = a.APOS_STRING_MODE = { cN: "string", b: "'", e: "'", i: "\\n", c: [a.BE] }, a.QSM = a.QUOTE_STRING_MODE = { cN: "string", b: '"', e: '"', i: "\\n", c: [a.BE] }, a.PWM = a.PHRASAL_WORDS_MODE = { b: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ }, a.C = a.COMMENT = function (e, n, t) { var r = a.inherit({ cN: "comment", b: e, e: n, c: [] }, t || {}); return r.c.push(a.PWM), r.c.push({ cN: "doctag", b: "(?:TODO|FIXME|NOTE|BUG|XXX):", relevance: 0 }), r }, a.CLCM = a.C_LINE_COMMENT_MODE = a.C("//", "$"), a.CBCM = a.C_BLOCK_COMMENT_MODE = a.C("/\\*", "\\*/"), a.HCM = a.HASH_COMMENT_MODE = a.C("#", "$"), a.NM = a.NUMBER_MODE = { cN: "number", b: a.NR, relevance: 0 }, a.CNM = a.C_NUMBER_MODE = { cN: "number", b: a.CNR, relevance: 0 }, a.BNM = a.BINARY_NUMBER_MODE = { cN: "number", b: a.BNR, relevance: 0 }, a.CSSNM = a.CSS_NUMBER_MODE = { cN: "number", b: a.NR + "(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", relevance: 0 }, a.RM = a.REGEXP_MODE = { cN: "regexp", b: /\//, e: /\/[gimuy]*/, i: /\n/, c: [a.BE, { b: /\[/, e: /\]/, relevance: 0, c: [a.BE] }] }, a.TM = a.TITLE_MODE = { cN: "title", b: a.IR, relevance: 0 }, a.UTM = a.UNDERSCORE_TITLE_MODE = { cN: "title", b: a.UIR, relevance: 0 }, a.METHOD_GUARD = { b: "\\.\\s*" + a.UIR, relevance: 0 }, [a.BE, a.ASM, a.QSM, a.PWM, a.C, a.CLCM, a.CBCM, a.HCM, a.NM, a.CNM, a.BNM, a.CSSNM, a.RM, a.TM, a.UTM, a.METHOD_GUARD].forEach(function (e) { !function n(t) { Object.freeze(t); var r = "function" == typeof t; Object.getOwnPropertyNames(t).forEach(function (e) { !t.hasOwnProperty(e) || null === t[e] || "object" != typeof t[e] && "function" != typeof t[e] || r && ("caller" === e || "callee" === e || "arguments" === e) || Object.isFrozen(t[e]) || n(t[e]) }); return t }(e) }), a }); hljs.registerLanguage("erlang", function (e) { var r = "[a-z'][a-zA-Z0-9_']*", c = "(" + r + ":" + r + "|" + r + ")", n = { keyword: "after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if let not of orelse|10 query receive rem try when xor", literal: "false true" }, a = e.C("%", "$"), b = { cN: "number", b: "\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)", relevance: 0 }, i = { b: "fun\\s+" + r + "/\\d+" }, l = { b: c + "\\(", e: "\\)", rB: !0, relevance: 0, c: [{ b: c, relevance: 0 }, { b: "\\(", e: "\\)", eW: !0, rE: !0, relevance: 0 }] }, d = { b: "{", e: "}", relevance: 0 }, o = { b: "\\b_([A-Z][A-Za-z0-9_]*)?", relevance: 0 }, t = { b: "[A-Z][a-zA-Z0-9_]*", relevance: 0 }, v = { b: "#" + e.UIR, relevance: 0, rB: !0, c: [{ b: "#" + e.UIR, relevance: 0 }, { b: "{", e: "}", relevance: 0 }] }, f = { bK: "fun receive if try case", e: "end", k: n }; f.c = [a, i, e.inherit(e.ASM, { cN: "" }), f, l, e.QSM, b, d, o, t, v]; var s = [a, i, f, l, e.QSM, b, d, o, t, v]; l.c[1].c = s, d.c = s; var u = { cN: "params", b: "\\(", e: "\\)", c: v.c[1].c = s }; return { aliases: ["erl"], k: n, i: "(", rB: !0, i: "\\(|#|//|/\\*|\\\\|:|;", c: [u, e.inherit(e.TM, { b: r })], starts: { e: ";|\\.", k: n, c: s } }, a, { b: "^-", e: "\\.", relevance: 0, eE: !0, rB: !0, l: "-" + e.IR, k: "-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec", c: [u] }, b, e.QSM, v, o, t, d, { b: /\.$/ }] } }); hljs.registerLanguage("bash", function (e) { var t = { cN: "variable", v: [{ b: /\$[\w\d#@][\w\d_]*/ }, { b: /\$\{(.*?)}/ }] }, a = { cN: "string", b: /"/, e: /"/, c: [e.BE, t, { cN: "variable", b: /\$\(/, e: /\)/, c: [e.BE] }] }; return { aliases: ["sh", "zsh"], l: /\b-?[a-z\._]+\b/, k: { keyword: "if then else elif fi for while in do done case esac function", literal: "true false", built_in: "break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp", _: "-ne -eq -lt -gt -f -d -e -s -l -a" }, c: [{ cN: "meta", b: /^#![^\n]+sh\s*$/, relevance: 10 }, { cN: "function", b: /\w[\w\d_]*\s*\(\s*\)\s*\{/, rB: !0, c: [e.inherit(e.TM, { b: /\w[\w\d_]*/ })], relevance: 0 }, e.HCM, a, { cN: "", b: /\\"/ }, { cN: "string", b: /'/, e: /'/ }, t] } }); hljs.registerLanguage("shell", function (s) { return { aliases: ["console"], c: [{ cN: "meta", b: "^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]", starts: { e: "$", sL: "bash" } }] } }); hljs.registerLanguage("scheme", function (e) { var t = "[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+", r = "(\\-|\\+)?\\d+([./]\\d+)?", a = { cN: "literal", b: "(#t|#f|#\\\\" + t + "|#\\\\.)" }, i = { cN: "number", v: [{ b: r, relevance: 0 }, { b: "(\\-|\\+)?\\d+([./]\\d+)?[+\\-](\\-|\\+)?\\d+([./]\\d+)?i", relevance: 0 }, { b: "#b[0-1]+(/[0-1]+)?" }, { b: "#o[0-7]+(/[0-7]+)?" }, { b: "#x[0-9a-f]+(/[0-9a-f]+)?" }] }, n = e.QSM, c = [e.C(";", "$", { relevance: 0 }), e.C("#\\|", "\\|#")], l = { b: t, relevance: 0 }, s = { cN: "symbol", b: "'" + t }, o = { eW: !0, relevance: 0 }, u = { v: [{ b: /'/ }, { b: "`" }], c: [{ b: "\\(", e: "\\)", c: ["self", a, n, i, l, s] }] }, p = { cN: "name", b: t, l: t, k: { "builtin-name": "case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?" } }, d = { v: [{ b: "\\(", e: "\\)" }, { b: "\\[", e: "\\]" }], c: [{ b: /lambda/, eW: !0, rB: !0, c: [p, { b: /\(/, e: /\)/, endsParent: !0, c: [l] }] }, p, o] }; return o.c = [a, i, n, l, s, u, d].concat(c), { i: /\S/, c: [{ cN: "meta", b: "^#!", e: "$" }, i, n, s, u, d].concat(c) } }); hljs.registerLanguage("basic", function (E) { return { cI: !0, i: "^.", l: "[a-zA-Z][a-zA-Z0-9_$%!#]*", k: { keyword: "ABS ASC AND ATN AUTO|0 BEEP BLOAD|10 BSAVE|10 CALL CALLS CDBL CHAIN CHDIR CHR$|10 CINT CIRCLE CLEAR CLOSE CLS COLOR COM COMMON CONT COS CSNG CSRLIN CVD CVI CVS DATA DATE$ DEFDBL DEFINT DEFSNG DEFSTR DEF|0 SEG USR DELETE DIM DRAW EDIT END ENVIRON ENVIRON$ EOF EQV ERASE ERDEV ERDEV$ ERL ERR ERROR EXP FIELD FILES FIX FOR|0 FRE GET GOSUB|10 GOTO HEX$ IF THEN ELSE|0 INKEY$ INP INPUT INPUT# INPUT$ INSTR IMP INT IOCTL IOCTL$ KEY ON OFF LIST KILL LEFT$ LEN LET LINE LLIST LOAD LOC LOCATE LOF LOG LPRINT USING LSET MERGE MID$ MKDIR MKD$ MKI$ MKS$ MOD NAME NEW NEXT NOISE NOT OCT$ ON OR PEN PLAY STRIG OPEN OPTION BASE OUT PAINT PALETTE PCOPY PEEK PMAP POINT POKE POS PRINT PRINT] PSET PRESET PUT RANDOMIZE READ REM RENUM RESET|0 RESTORE RESUME RETURN|0 RIGHT$ RMDIR RND RSET RUN SAVE SCREEN SGN SHELL SIN SOUND SPACE$ SPC SQR STEP STICK STOP STR$ STRING$ SWAP SYSTEM TAB TAN TIME$ TIMER TROFF TRON TO USR VAL VARPTR VARPTR$ VIEW WAIT WHILE WEND WIDTH WINDOW WRITE XOR" }, c: [E.QSM, E.C("REM", "$", { relevance: 10 }), E.C("'", "$", { relevance: 0 }), { cN: "symbol", b: "^[0-9]+ ", relevance: 10 }, { cN: "number", b: "\\b([0-9]+[0-9edED.]*[#!]?)", relevance: 0 }, { cN: "number", b: "(&[hH][0-9a-fA-F]{1,4})" }, { cN: "number", b: "(&[oO][0-7]{1,6})" }] } }); hljs.registerLanguage("swift", function (e) { var i = { keyword: "#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet", literal: "true false nil", built_in: "abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip" }, t = e.C("/\\*", "\\*/", { c: ["self"] }), n = { cN: "subst", b: /\\\(/, e: "\\)", k: i, c: [] }, r = { cN: "string", c: [e.BE, n], v: [{ b: /"""/, e: /"""/ }, { b: /"/, e: /"/ }] }, a = { cN: "number", b: "\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b", relevance: 0 }; return n.c = [a], { k: i, c: [r, e.CLCM, t, { cN: "type", b: "\\b[A-Z][\\wÀ-ʸ']*[!?]" }, { cN: "type", b: "\\b[A-Z][\\wÀ-ʸ']*", relevance: 0 }, a, { cN: "function", bK: "func", e: "{", eE: !0, c: [e.inherit(e.TM, { b: /[A-Za-z$_][0-9A-Za-z$_]*/ }), { b: // }, { cN: "params", b: /\(/, e: /\)/, endsParent: !0, k: i, c: ["self", a, r, e.CBCM, { b: ":" }], i: /["']/ }], i: /\[|%/ }, { cN: "class", bK: "struct protocol class extension enum", k: i, e: "\\{", eE: !0, c: [e.inherit(e.TM, { b: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/ })] }, { cN: "meta", b: "(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)" }, { bK: "import", e: /$/, c: [e.CLCM, t] }] } }); hljs.registerLanguage("ini", function (e) { var b = { cN: "number", relevance: 0, v: [{ b: /([\+\-]+)?[\d]+_[\d_]+/ }, { b: e.NR }] }, a = e.C(); a.v = [{ b: /;/, e: /$/ }, { b: /#/, e: /$/ }]; var c = { cN: "variable", v: [{ b: /\$[\w\d"][\w\d_]*/ }, { b: /\$\{(.*?)}/ }] }, r = { cN: "literal", b: /\bon|off|true|false|yes|no\b/ }, n = { cN: "string", c: [e.BE], v: [{ b: "'''", e: "'''", relevance: 10 }, { b: '"""', e: '"""', relevance: 10 }, { b: '"', e: '"' }, { b: "'", e: "'" }] }; return { aliases: ["toml"], cI: !0, i: /\S/, c: [a, { cN: "section", b: /\[+/, e: /\]+/ }, { b: /^[a-z0-9\[\]_\.-]+(?=\s*=\s*)/, cN: "attr", starts: { e: /$/, c: [a, { b: /\[/, e: /\]/, c: [a, r, c, n, b, "self"], relevance: 0 }, r, c, n, b] } }] } }); hljs.registerLanguage("ruby", function (e) { var c = "[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?", b = { keyword: "and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor", literal: "true false nil" }, r = { cN: "doctag", b: "@[A-Za-z]+" }, a = { b: "#<", e: ">" }, n = [e.C("#", "$", { c: [r] }), e.C("^\\=begin", "^\\=end", { c: [r], relevance: 10 }), e.C("^__END__", "\\n$")], s = { cN: "subst", b: "#\\{", e: "}", k: b }, t = { cN: "string", c: [e.BE, s], v: [{ b: /'/, e: /'/ }, { b: /"/, e: /"/ }, { b: /`/, e: /`/ }, { b: "%[qQwWx]?\\(", e: "\\)" }, { b: "%[qQwWx]?\\[", e: "\\]" }, { b: "%[qQwWx]?{", e: "}" }, { b: "%[qQwWx]?<", e: ">" }, { b: "%[qQwWx]?/", e: "/" }, { b: "%[qQwWx]?%", e: "%" }, { b: "%[qQwWx]?-", e: "-" }, { b: "%[qQwWx]?\\|", e: "\\|" }, { b: /\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/ }, { b: /<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/, rB: !0, c: [{ b: /<<[-~]?'?/ }, { b: /\w+/, endSameAsBegin: !0, c: [e.BE, s] }] }] }, i = { cN: "params", b: "\\(", e: "\\)", endsParent: !0, k: b }, l = [t, a, { cN: "class", bK: "class module", e: "$|;", i: /=/, c: [e.inherit(e.TM, { b: "[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?" }), { b: "<\\s*", c: [{ b: "(" + e.IR + "::)?" + e.IR }] }].concat(n) }, { cN: "function", bK: "def", e: "$|;", c: [e.inherit(e.TM, { b: c }), i].concat(n) }, { b: e.IR + "::" }, { cN: "symbol", b: e.UIR + "(\\!|\\?)?:", relevance: 0 }, { cN: "symbol", b: ":(?!\\s)", c: [t, { b: c }], relevance: 0 }, { cN: "number", b: "(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", relevance: 0 }, { b: "(\\$\\W)|((\\$|\\@\\@?)(\\w+))" }, { cN: "params", b: /\|/, e: /\|/, k: b }, { b: "(" + e.RSR + "|unless)\\s*", k: "unless", c: [a, { cN: "regexp", c: [e.BE, s], i: /\n/, v: [{ b: "/", e: "/[a-z]*" }, { b: "%r{", e: "}[a-z]*" }, { b: "%r\\(", e: "\\)[a-z]*" }, { b: "%r!", e: "![a-z]*" }, { b: "%r\\[", e: "\\][a-z]*" }] }].concat(n), relevance: 0 }].concat(n); s.c = l; var d = [{ b: /^\s*=>/, starts: { e: "$", c: i.c = l } }, { cN: "meta", b: "^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)", starts: { e: "$", c: l } }]; return { aliases: ["rb", "gemspec", "podspec", "thor", "irb"], k: b, i: /\/\*/, c: n.concat(d).concat(l) } }); hljs.registerLanguage("plaintext", function (e) { return { disableAutodetect: !0 } }); hljs.registerLanguage("d", function (e) { var a = "(0|[1-9][\\d_]*)", d = "(" + a + "|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))", t = "\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};", _ = { cN: "number", b: "\\b" + d + "(L|u|U|Lu|LU|uL|UL)?", relevance: 0 }, r = { cN: "number", b: "\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|" + d + "(i|[fF]i|Li))", relevance: 0 }, i = { cN: "string", b: "'(" + t + "|.)", e: "'", i: "." }, n = { cN: "string", b: '"', c: [{ b: t, relevance: 0 }], e: '"[cwd]?' }, c = e.C("\\/\\+", "\\+\\/", { c: ["self"], relevance: 10 }); return { l: e.UIR, k: { keyword: "abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__", built_in: "bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring", literal: "false null true" }, c: [e.CLCM, e.CBCM, c, { cN: "string", b: 'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?', relevance: 10 }, n, { cN: "string", b: '[rq]"', e: '"[cwd]?', relevance: 5 }, { cN: "string", b: "`", e: "`[cwd]?" }, { cN: "string", b: 'q"\\{', e: '\\}"' }, r, _, i, { cN: "meta", b: "^#!", e: "$", relevance: 5 }, { cN: "meta", b: "#(line)", e: "$", relevance: 5 }, { cN: "keyword", b: "@[a-zA-Z_][a-zA-Z_\\d]*" }] } }); hljs.registerLanguage("scala", function (e) { var t = { cN: "subst", v: [{ b: "\\$[A-Za-z0-9_]+" }, { b: "\\${", e: "}" }] }, a = { cN: "string", v: [{ b: '"', e: '"', i: "\\n", c: [e.BE] }, { b: '"""', e: '"""', relevance: 10 }, { b: '[a-z]+"', e: '"', i: "\\n", c: [e.BE, t] }, { cN: "string", b: '[a-z]+"""', e: '"""', c: [t], relevance: 10 }] }, c = { cN: "type", b: "\\b[A-Z][A-Za-z0-9_]*", relevance: 0 }, r = { cN: "title", b: /[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/, relevance: 0 }, n = { cN: "class", bK: "class object trait type", e: /[:={\[\n;]/, eE: !0, c: [{ bK: "extends with", relevance: 10 }, { b: /\[/, e: /\]/, eB: !0, eE: !0, relevance: 0, c: [c] }, { cN: "params", b: /\(/, e: /\)/, eB: !0, eE: !0, relevance: 0, c: [c] }, r] }, l = { cN: "function", bK: "def", e: /[:={\[(\n;]/, eE: !0, c: [r] }; return { k: { literal: "true false null", keyword: "type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit" }, c: [e.CLCM, e.CBCM, a, { cN: "symbol", b: "'\\w[\\w\\d_]*(?!')" }, c, l, n, e.CNM, { cN: "meta", b: "@[A-Za-z]+" }] } }); hljs.registerLanguage("cs", function (e) { var a = { keyword: "abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield", literal: "null false true" }, i = { cN: "number", v: [{ b: "\\b(0b[01']+)" }, { b: "(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)" }, { b: "(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" }], relevance: 0 }, c = { cN: "string", b: '@"', e: '"', c: [{ b: '""' }] }, r = e.inherit(c, { i: /\n/ }), n = { cN: "subst", b: "{", e: "}", k: a }, t = e.inherit(n, { i: /\n/ }), s = { cN: "string", b: /\$"/, e: '"', i: /\n/, c: [{ b: "{{" }, { b: "}}" }, e.BE, t] }, l = { cN: "string", b: /\$@"/, e: '"', c: [{ b: "{{" }, { b: "}}" }, { b: '""' }, n] }, b = e.inherit(l, { i: /\n/, c: [{ b: "{{" }, { b: "}}" }, { b: '""' }, t] }); n.c = [l, s, c, e.ASM, e.QSM, i, e.CBCM], t.c = [b, s, r, e.ASM, e.QSM, i, e.inherit(e.CBCM, { i: /\n/ })]; var o = { v: [l, s, c, e.ASM, e.QSM] }, d = e.IR + "(<" + e.IR + "(\\s*,\\s*" + e.IR + ")*>)?(\\[\\])?"; return { aliases: ["csharp", "c#"], k: a, i: /::/, c: [e.C("///", "$", { rB: !0, c: [{ cN: "doctag", v: [{ b: "///", relevance: 0 }, { b: "\x3c!--|--\x3e" }, { b: "" }] }] }), e.CLCM, e.CBCM, { cN: "meta", b: "#", e: "$", k: { "meta-keyword": "if else elif endif define undef warning error line region endregion pragma checksum" } }, o, i, { bK: "class interface", e: /[{;=]/, i: /[^\s:,]/, c: [e.TM, e.CLCM, e.CBCM] }, { bK: "namespace", e: /[{;=]/, i: /[^\s:]/, c: [e.inherit(e.TM, { b: "[a-zA-Z](\\.?\\w)*" }), e.CLCM, e.CBCM] }, { cN: "meta", b: "^\\s*\\[", eB: !0, e: "\\]", eE: !0, c: [{ cN: "meta-string", b: /"/, e: /"/ }] }, { bK: "new return throw await else", relevance: 0 }, { cN: "function", b: "(" + d + "\\s+)+" + e.IR + "\\s*\\(", rB: !0, e: /\s*[{;=]/, eE: !0, k: a, c: [{ b: e.IR + "\\s*\\(", rB: !0, c: [e.TM], relevance: 0 }, { cN: "params", b: /\(/, e: /\)/, eB: !0, eE: !0, k: a, relevance: 0, c: [o, i, e.CBCM] }, e.CLCM, e.CBCM] }] } }); hljs.registerLanguage("cpp", function (e) { function t(e) { return "(?:" + e + ")?" } var r = "decltype\\(auto\\)", a = "[a-zA-Z_]\\w*::", i = (t(a), t("<.*?>"), { cN: "keyword", b: "\\b[a-z\\d_]*_t\\b" }), c = { cN: "string", v: [{ b: '(u8?|U|L)?"', e: '"', i: "\\n", c: [e.BE] }, { b: "(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", e: "'", i: "." }, { b: /(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/ }] }, s = { cN: "number", v: [{ b: "\\b(0b[01']+)" }, { b: "(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)" }, { b: "(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" }], relevance: 0 }, n = { cN: "meta", b: /#\s*[a-z]+\b/, e: /$/, k: { "meta-keyword": "if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" }, c: [{ b: /\\\n/, relevance: 0 }, e.inherit(c, { cN: "meta-string" }), { cN: "meta-string", b: /<.*?>/, e: /$/, i: "\\n" }, e.CLCM, e.CBCM] }, o = { cN: "title", b: t(a) + e.IR, relevance: 0 }, l = t(a) + e.IR + "\\s*\\(", u = { keyword: "int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_tshort reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq", built_in: "std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary", literal: "true false nullptr NULL" }, p = [i, e.CLCM, e.CBCM, s, c], m = { v: [{ b: /=/, e: /;/ }, { b: /\(/, e: /\)/ }, { bK: "new throw return else", e: /;/ }], k: u, c: p.concat([{ b: /\(/, e: /\)/, k: u, c: p.concat(["self"]), relevance: 0 }]), relevance: 0 }, d = { cN: "function", b: "((decltype\\(auto\\)|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+" + l, rB: !0, e: /[{;=]/, eE: !0, k: u, i: /[^\w\s\*&:<>]/, c: [{ b: r, k: u, relevance: 0 }, { b: l, rB: !0, c: [o], relevance: 0 }, { cN: "params", b: /\(/, e: /\)/, k: u, relevance: 0, c: [e.CLCM, e.CBCM, c, s, i, { b: /\(/, e: /\)/, k: u, relevance: 0, c: ["self", e.CLCM, e.CBCM, c, s, i] }] }, i, e.CLCM, e.CBCM, n] }; return { aliases: ["c", "cc", "h", "c++", "h++", "hpp", "hh", "hxx", "cxx"], k: u, i: "", k: u, c: ["self", i] }, { b: e.IR + "::", k: u }, { cN: "class", bK: "class struct", e: /[{;:]/, c: [{ b: //, c: ["self"] }, e.TM] }]), exports: { preprocessor: n, strings: c, k: u } } }); hljs.registerLanguage("less", function (e) { function r(e) { return { cN: "string", b: "~?" + e + ".*?" + e } } function t(e, r, t) { return { cN: e, b: r, relevance: t } } var a = "[\\w-]+", c = "(" + a + "|@{" + a + "})", s = [], n = [], b = { b: "\\(", e: "\\)", c: n, relevance: 0 }; n.push(e.CLCM, e.CBCM, r("'"), r('"'), e.CSSNM, { b: "(url|data-uri)\\(", starts: { cN: "string", e: "[\\)\\n]", eE: !0 } }, t("number", "#[0-9A-Fa-f]+\\b"), b, t("variable", "@@?" + a, 10), t("variable", "@{" + a + "}"), t("built_in", "~?`[^`]*?`"), { cN: "attribute", b: a + "\\s*:", e: ":", rB: !0, eE: !0 }, { cN: "meta", b: "!important" }); var i = n.concat({ b: "{", e: "}", c: s }), l = { bK: "when", eW: !0, c: [{ bK: "and not" }].concat(n) }, o = { b: c + "\\s*:", rB: !0, e: "[;}]", relevance: 0, c: [{ cN: "attribute", b: c, e: ":", eE: !0, starts: { eW: !0, i: "[<=$]", relevance: 0, c: n } }] }, u = { cN: "keyword", b: "@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b", starts: { e: "[;{}]", rE: !0, c: n, relevance: 0 } }, v = { cN: "variable", v: [{ b: "@" + a + "\\s*:", relevance: 15 }, { b: "@" + a }], starts: { e: "[;}]", rE: !0, c: i } }, C = { v: [{ b: "[\\.#:&\\[>]", e: "[;{}]" }, { b: c, e: "{" }], rB: !0, rE: !0, i: "[<='$\"]", relevance: 0, c: [e.CLCM, e.CBCM, l, t("keyword", "all\\b"), t("variable", "@{" + a + "}"), t("selector-tag", c + "%?", 0), t("selector-id", "#" + c), t("selector-class", "\\." + c, 0), t("selector-tag", "&", 0), { cN: "selector-attr", b: "\\[", e: "\\]" }, { cN: "selector-pseudo", b: /:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/ }, { b: "\\(", e: "\\)", c: i }, { b: "!important" }] }; return s.push(e.CLCM, e.CBCM, u, v, o, C), { cI: !0, i: "[=>'/<($\"]", c: s } }); hljs.registerLanguage("python", function (e) { var r = { keyword: "and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10", built_in: "Ellipsis NotImplemented", literal: "False None True" }, b = { cN: "meta", b: /^(>>>|\.\.\.) / }, c = { cN: "subst", b: /\{/, e: /\}/, k: r, i: /#/ }, a = { b: /\{\{/, relevance: 0 }, l = { cN: "string", c: [e.BE], v: [{ b: /(u|b)?r?'''/, e: /'''/, c: [e.BE, b], relevance: 10 }, { b: /(u|b)?r?"""/, e: /"""/, c: [e.BE, b], relevance: 10 }, { b: /(fr|rf|f)'''/, e: /'''/, c: [e.BE, b, a, c] }, { b: /(fr|rf|f)"""/, e: /"""/, c: [e.BE, b, a, c] }, { b: /(u|r|ur)'/, e: /'/, relevance: 10 }, { b: /(u|r|ur)"/, e: /"/, relevance: 10 }, { b: /(b|br)'/, e: /'/ }, { b: /(b|br)"/, e: /"/ }, { b: /(fr|rf|f)'/, e: /'/, c: [e.BE, a, c] }, { b: /(fr|rf|f)"/, e: /"/, c: [e.BE, a, c] }, e.ASM, e.QSM] }, n = { cN: "number", relevance: 0, v: [{ b: e.BNR + "[lLjJ]?" }, { b: "\\b(0o[0-7]+)[lLjJ]?" }, { b: e.CNR + "[lLjJ]?" }] }, i = { cN: "params", b: /\(/, e: /\)/, c: ["self", b, n, l, e.HCM] }; return c.c = [l, n, b], { aliases: ["py", "gyp", "ipython"], k: r, i: /(<\/|->|\?)|=>/, c: [b, n, { bK: "if", relevance: 0 }, l, e.HCM, { v: [{ cN: "function", bK: "def" }, { cN: "class", bK: "class" }], e: /:/, i: /[${=;\n,]/, c: [e.UTM, i, { b: /->/, eW: !0, k: "None" }] }, { cN: "meta", b: /^[\t ]*@/, e: /$/ }, { b: /\b(print|exec)\(/ }] } }); hljs.registerLanguage("powershell", function (e) { var t = { keyword: "if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter" }, n = { b: "`[\\s\\S]", relevance: 0 }, c = { cN: "variable", v: [{ b: /\$\B/ }, { cN: "keyword", b: /\$this/ }, { b: /\$[\w\d][\w\d_:]*/ }] }, i = { cN: "string", v: [{ b: /"/, e: /"/ }, { b: /@"/, e: /^"@/ }], c: [n, c, { cN: "variable", b: /\$[A-z]/, e: /[^A-z]/ }] }, a = { cN: "string", v: [{ b: /'/, e: /'/ }, { b: /@'/, e: /^'@/ }] }, r = e.inherit(e.C(null, null), { v: [{ b: /#/, e: /$/ }, { b: /<#/, e: /#>/ }], c: [{ cN: "doctag", v: [{ b: /\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/ }, { b: /\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/ }] }] }), o = { cN: "built_in", v: [{ b: "(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|New|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Complete|Confirm|Deny|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where", ")+(-)[\\w\\d]+") }] }, l = { cN: "class", bK: "class enum", e: /\s*[{]/, eE: !0, relevance: 0, c: [e.TM] }, s = { cN: "function", b: /function\s+/, e: /\s*\{|$/, eE: !0, rB: !0, relevance: 0, c: [{ b: "function", relevance: 0, cN: "keyword" }, { cN: "title", b: /\w[\w\d]*((-)[\w\d]+)*/, relevance: 0 }, { b: /\(/, e: /\)/, cN: "params", relevance: 0, c: [c] }] }, p = { b: /using\s/, e: /$/, rB: !0, c: [i, a, { cN: "keyword", b: /(using|assembly|command|module|namespace|type)/ }] }, b = { v: [{ cN: "operator", b: "(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor", ")\\b") }, { cN: "literal", b: /(-)[\w\d]+/, relevance: 0 }] }, d = { cN: "function", b: /\[.*\]\s*[\w]+[ ]??\(/, e: /$/, rB: !0, relevance: 0, c: [{ cN: "keyword", b: "(".concat(t.keyword.toString().replace(/\s/g, "|"), ")\\b"), endsParent: !0, relevance: 0 }, e.inherit(e.TM, { endsParent: !0 })] }, u = [d, r, n, e.NM, i, a, o, c, { cN: "literal", b: /\$(null|true|false)\b/ }, { cN: "selector-tag", b: /\@\B/, relevance: 0 }], m = { b: /\[/, e: /\]/, eB: !0, eE: !0, relevance: 0, c: [].concat("self", u, { b: "(" + ["string", "char", "byte", "int", "long", "bool", "decimal", "single", "double", "DateTime", "xml", "array", "hashtable", "void"].join("|") + ")", cN: "built_in", relevance: 0 }, { cN: "type", b: /[\.\w\d]+/, relevance: 0 }) }; return d.c.unshift(m), { aliases: ["ps", "ps1"], l: /-?[A-z\.\-]+/, cI: !0, k: t, c: u.concat(l, s, p, b, m) } }); hljs.registerLanguage("coffeescript", function (e) { var c = { keyword: "in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not", literal: "true false null undefined yes no on off", built_in: "npm require console print module global window document" }, n = "[A-Za-z$_][0-9A-Za-z$_]*", r = { cN: "subst", b: /#\{/, e: /}/, k: c }, i = [e.BNM, e.inherit(e.CNM, { starts: { e: "(\\s*/)?", relevance: 0 } }), { cN: "string", v: [{ b: /'''/, e: /'''/, c: [e.BE] }, { b: /'/, e: /'/, c: [e.BE] }, { b: /"""/, e: /"""/, c: [e.BE, r] }, { b: /"/, e: /"/, c: [e.BE, r] }] }, { cN: "regexp", v: [{ b: "///", e: "///", c: [r, e.HCM] }, { b: "//[gim]{0,3}(?=\\W)", relevance: 0 }, { b: /\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/ }] }, { b: "@" + n }, { sL: "javascript", eB: !0, eE: !0, v: [{ b: "```", e: "```" }, { b: "`", e: "`" }] }]; r.c = i; var s = e.inherit(e.TM, { b: n }), t = "(\\(.*\\))?\\s*\\B[-=]>", a = { cN: "params", b: "\\([^\\(]", rB: !0, c: [{ b: /\(/, e: /\)/, k: c, c: ["self"].concat(i) }] }; return { aliases: ["coffee", "cson", "iced"], k: c, i: /\/\*/, c: i.concat([e.C("###", "###"), e.HCM, { cN: "function", b: "^\\s*" + n + "\\s*=\\s*" + t, e: "[-=]>", rB: !0, c: [s, a] }, { b: /[:\(,=]\s*/, relevance: 0, c: [{ cN: "function", b: t, e: "[-=]>", rB: !0, c: [a] }] }, { cN: "class", bK: "class", e: "$", i: /[:="\[\]]/, c: [{ bK: "extends", eW: !0, i: /[:="\[\]]/, c: [s] }, s] }, { b: n + ":", e: ":", rB: !0, rE: !0, relevance: 0 }]) } }); hljs.registerLanguage("json", function (e) { var i = { literal: "true false null" }, n = [e.CLCM, e.CBCM], c = [e.QSM, e.CNM], r = { e: ",", eW: !0, eE: !0, c: c, k: i }, t = { b: "{", e: "}", c: [{ cN: "attr", b: /"/, e: /"/, c: [e.BE], i: "\\n" }, e.inherit(r, { b: /:/ })].concat(n), i: "\\S" }, a = { b: "\\[", e: "\\]", c: [e.inherit(r)], i: "\\S" }; return c.push(t, a), n.forEach(function (e) { c.push(e) }), { c: c, k: i, i: "\\S" } }); hljs.registerLanguage("xml", function (e) { var c = { cN: "symbol", b: "&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;" }, s = { b: "\\s", c: [{ cN: "meta-keyword", b: "#?[a-z_][a-z1-9_-]+", i: "\\n" }] }, a = e.inherit(s, { b: "\\(", e: "\\)" }), t = e.inherit(e.ASM, { cN: "meta-string" }), l = e.inherit(e.QSM, { cN: "meta-string" }), r = { eW: !0, i: /`]+/ }] }] }] }; return { aliases: ["html", "xhtml", "rss", "atom", "xjb", "xsd", "xsl", "plist", "wsf", "svg"], cI: !0, c: [{ cN: "meta", b: "", relevance: 10, c: [s, l, t, a, { b: "\\[", e: "\\]", c: [{ cN: "meta", b: "", c: [s, a, l, t] }] }] }, e.C("\x3c!--", "--\x3e", { relevance: 10 }), { b: "<\\!\\[CDATA\\[", e: "\\]\\]>", relevance: 10 }, c, { cN: "meta", b: /<\?xml/, e: /\?>/, relevance: 10 }, { b: /<\?(php)?/, e: /\?>/, sL: "php", c: [{ b: "/\\*", e: "\\*/", skip: !0 }, { b: 'b"', e: '"', skip: !0 }, { b: "b'", e: "'", skip: !0 }, e.inherit(e.ASM, { i: null, cN: null, c: null, skip: !0 }), e.inherit(e.QSM, { i: null, cN: null, c: null, skip: !0 })] }, { cN: "tag", b: ")", e: ">", k: { name: "style" }, c: [r], starts: { e: "", rE: !0, sL: ["css", "xml"] } }, { cN: "tag", b: ")", e: ">", k: { name: "script" }, c: [r], starts: { e: "<\/script>", rE: !0, sL: ["actionscript", "javascript", "handlebars", "xml"] } }, { cN: "tag", b: "", c: [{ cN: "name", b: /[^\/><\s]+/, relevance: 0 }, r] }] } }); hljs.registerLanguage("markdown", function (e) { return { aliases: ["md", "mkdown", "mkd"], c: [{ cN: "section", v: [{ b: "^#{1,6}", e: "$" }, { b: "^.+?\\n[=-]{2,}$" }] }, { b: "<", e: ">", sL: "xml", relevance: 0 }, { cN: "bullet", b: "^\\s*([*+-]|(\\d+\\.))\\s+" }, { cN: "strong", b: "[*_]{2}.+?[*_]{2}" }, { cN: "emphasis", v: [{ b: "\\*.+?\\*" }, { b: "_.+?_", relevance: 0 }] }, { cN: "quote", b: "^>\\s+", e: "$" }, { cN: "code", v: [{ b: "^```\\w*\\s*$", e: "^```[ ]*$" }, { b: "`.+?`" }, { b: "^( {4}|\\t)", e: "$", relevance: 0 }] }, { b: "^[-\\*]{3,}", e: "$" }, { b: "\\[.+?\\][\\(\\[].*?[\\)\\]]", rB: !0, c: [{ cN: "string", b: "\\[", e: "\\]", eB: !0, rE: !0, relevance: 0 }, { cN: "link", b: "\\]\\(", e: "\\)", eB: !0, eE: !0 }, { cN: "symbol", b: "\\]\\[", e: "\\]", eB: !0, eE: !0 }], relevance: 10 }, { b: /^\[[^\n]+\]:/, rB: !0, c: [{ cN: "symbol", b: /\[/, e: /\]/, eB: !0, eE: !0 }, { cN: "link", b: /:\s*/, e: /$/, eB: !0 }] }] } }); hljs.registerLanguage("sql", function (e) { var t = e.C("--", "$"); return { cI: !0, i: /[<>{}*]/, c: [{ bK: "begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with", e: /;/, eW: !0, l: /[\w\.]+/, k: { keyword: "as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek", literal: "true false null unknown", built_in: "array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void" }, c: [{ cN: "string", b: "'", e: "'", c: [{ b: "''" }] }, { cN: "string", b: '"', e: '"', c: [{ b: '""' }] }, { cN: "string", b: "`", e: "`" }, e.CNM, e.CBCM, t, e.HCM] }, e.CBCM, t, e.HCM] } }); hljs.registerLanguage("javascript", function (e) { var r = "<>", a = "", t = { b: /<[A-Za-z0-9\\._:-]+/, e: /\/[A-Za-z0-9\\._:-]+>|\/>/ }, c = "[A-Za-z$_][0-9A-Za-z$_]*", n = { keyword: "in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as", literal: "true false null undefined NaN Infinity", built_in: "eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise" }, s = { cN: "number", v: [{ b: "\\b(0[bB][01]+)n?" }, { b: "\\b(0[oO][0-7]+)n?" }, { b: e.CNR + "n?" }], relevance: 0 }, o = { cN: "subst", b: "\\$\\{", e: "\\}", k: n, c: [] }, i = { b: "html`", e: "", starts: { e: "`", rE: !1, c: [e.BE, o], sL: "xml" } }, b = { b: "css`", e: "", starts: { e: "`", rE: !1, c: [e.BE, o], sL: "css" } }, l = { cN: "string", b: "`", e: "`", c: [e.BE, o] }; o.c = [e.ASM, e.QSM, i, b, l, s, e.RM]; var u = o.c.concat([e.CBCM, e.CLCM]); return { aliases: ["js", "jsx", "mjs", "cjs"], k: n, c: [{ cN: "meta", relevance: 10, b: /^\s*['"]use (strict|asm)['"]/ }, { cN: "meta", b: /^#!/, e: /$/ }, e.ASM, e.QSM, i, b, l, e.CLCM, e.C("/\\*\\*", "\\*/", { relevance: 0, c: [{ cN: "doctag", b: "@[A-Za-z]+", c: [{ cN: "type", b: "\\{", e: "\\}", relevance: 0 }, { cN: "variable", b: c + "(?=\\s*(-)|$)", endsParent: !0, relevance: 0 }, { b: /(?=[^\n])\s/, relevance: 0 }] }] }), e.CBCM, s, { b: /[{,\n]\s*/, relevance: 0, c: [{ b: c + "\\s*:", rB: !0, relevance: 0, c: [{ cN: "attr", b: c, relevance: 0 }] }] }, { b: "(" + e.RSR + "|\\b(case|return|throw)\\b)\\s*", k: "return throw case", c: [e.CLCM, e.CBCM, e.RM, { cN: "function", b: "(\\(.*?\\)|" + c + ")\\s*=>", rB: !0, e: "\\s*=>", c: [{ cN: "params", v: [{ b: c }, { b: /\(\s*\)/ }, { b: /\(/, e: /\)/, eB: !0, eE: !0, k: n, c: u }] }] }, { cN: "", b: /\s/, e: /\s*/, skip: !0 }, { v: [{ b: r, e: a }, { b: t.b, e: t.e }], sL: "xml", c: [{ b: t.b, e: t.e, skip: !0, c: ["self"] }] }], relevance: 0 }, { cN: "function", bK: "function", e: /\{/, eE: !0, c: [e.inherit(e.TM, { b: c }), { cN: "params", b: /\(/, e: /\)/, eB: !0, eE: !0, c: u }], i: /\[|%/ }, { b: /\$[(.]/ }, e.METHOD_GUARD, { cN: "class", bK: "class", e: /[{;=]/, eE: !0, i: /[:"\[\]]/, c: [{ bK: "extends" }, e.UTM] }, { bK: "constructor get set", e: /\{/, eE: !0 }], i: /#(?!!)/ } }); hljs.registerLanguage("perl", function (e) { var t = "getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when", r = { cN: "subst", b: "[$@]\\{", e: "\\}", k: t }, s = { b: "->{", e: "}" }, n = { v: [{ b: /\$\d/ }, { b: /[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/ }, { b: /[\$%@][^\s\w{]/, relevance: 0 }] }, c = [e.BE, r, n], a = [n, e.HCM, e.C("^\\=\\w", "\\=cut", { eW: !0 }), s, { cN: "string", c: c, v: [{ b: "q[qwxr]?\\s*\\(", e: "\\)", relevance: 5 }, { b: "q[qwxr]?\\s*\\[", e: "\\]", relevance: 5 }, { b: "q[qwxr]?\\s*\\{", e: "\\}", relevance: 5 }, { b: "q[qwxr]?\\s*\\|", e: "\\|", relevance: 5 }, { b: "q[qwxr]?\\s*\\<", e: "\\>", relevance: 5 }, { b: "qw\\s+q", e: "q", relevance: 5 }, { b: "'", e: "'", c: [e.BE] }, { b: '"', e: '"' }, { b: "`", e: "`", c: [e.BE] }, { b: "{\\w+}", c: [], relevance: 0 }, { b: "-?\\w+\\s*\\=\\>", c: [], relevance: 0 }] }, { cN: "number", b: "(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", relevance: 0 }, { b: "(\\/\\/|" + e.RSR + "|\\b(split|return|print|reverse|grep)\\b)\\s*", k: "split return print reverse grep", relevance: 0, c: [e.HCM, { cN: "regexp", b: "(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*", relevance: 10 }, { cN: "regexp", b: "(m|qr)?/", e: "/[a-z]*", c: [e.BE], relevance: 0 }] }, { cN: "function", bK: "sub", e: "(\\s*\\(.*?\\))?[;{]", eE: !0, relevance: 5, c: [e.TM] }, { b: "-\\w\\b", relevance: 0 }, { b: "^__DATA__$", e: "^__END__$", sL: "mojolicious", c: [{ b: "^@@.*", e: "$", cN: "comment" }] }]; return r.c = a, { aliases: ["pl", "pm"], l: /[\w\.]+/, k: t, c: s.c = a } }); hljs.registerLanguage("tex", function (e) { var c = { cN: "tag", b: /\\/, relevance: 0, c: [{ cN: "name", v: [{ b: /[a-zA-Z\u0430-\u044f\u0410-\u042f]+[*]?/ }, { b: /[^a-zA-Z\u0430-\u044f\u0410-\u042f0-9]/ }], starts: { eW: !0, relevance: 0, c: [{ cN: "string", v: [{ b: /\[/, e: /\]/ }, { b: /\{/, e: /\}/ }] }, { b: /\s*=\s*/, eW: !0, relevance: 0, c: [{ cN: "number", b: /-?\d*\.?\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?/ }] }] } }] }; return { c: [c, { cN: "formula", c: [c], relevance: 0, v: [{ b: /\$\$/, e: /\$\$/ }, { b: /\$/, e: /\$/ }] }, e.C("%", "$", { relevance: 0 })] } }); hljs.registerLanguage("fortran", function (e) { return { cI: !0, aliases: ["f90", "f95"], k: { literal: ".False. .True.", keyword: "kind do while private call intrinsic where elsewhere type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then block endblock public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. goto save else use module select case access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit continue format pause cycle exit c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg synchronous nopass non_overridable pass protected volatile abstract extends import non_intrinsic value deferred generic final enumerator class associate bind enum c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure integer real character complex logical dimension allocatable|10 parameter external implicit|10 none double precision assign intent optional pointer target in out common equivalence data", built_in: "alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_ofacosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr num_images parity popcnt poppar shifta shiftl shiftr this_image" }, i: /\/\*/, c: [e.inherit(e.ASM, { cN: "string", relevance: 0 }), e.inherit(e.QSM, { cN: "string", relevance: 0 }), { cN: "function", bK: "subroutine function program", i: "[${=\\n]", c: [e.UTM, { cN: "params", b: "\\(", e: "\\)" }] }, e.C("!", "$", { relevance: 0 }), { cN: "number", b: "(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?", relevance: 0 }] } }); hljs.registerLanguage("protobuf", function (e) { return { k: { keyword: "package import option optional required repeated group oneof", built_in: "double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes", literal: "true false" }, c: [e.QSM, e.NM, e.CLCM, { cN: "class", bK: "message enum service", e: /\{/, i: /\n/, c: [e.inherit(e.TM, { starts: { eW: !0, eE: !0 } })] }, { cN: "function", bK: "rpc", e: /;/, eE: !0, k: "rpc returns" }, { b: /^\s*[A-Z_]+/, e: /\s*=/, eE: !0 }] } }); hljs.registerLanguage("java", function (e) { var a = "false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do", t = { cN: "number", b: "\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?", relevance: 0 }; return { aliases: ["jsp"], k: a, i: /<\/|#/, c: [e.C("/\\*\\*", "\\*/", { relevance: 0, c: [{ b: /\w+@/, relevance: 0 }, { cN: "doctag", b: "@[A-Za-z]+" }] }), e.CLCM, e.CBCM, e.ASM, e.QSM, { cN: "class", bK: "class interface", e: /[{;=]/, eE: !0, k: "class interface", i: /[:"\[\]]/, c: [{ bK: "extends implements" }, e.UTM] }, { bK: "new throw return else", relevance: 0 }, { cN: "function", b: "([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+" + e.UIR + "\\s*\\(", rB: !0, e: /[{;=]/, eE: !0, k: a, c: [{ b: e.UIR + "\\s*\\(", rB: !0, relevance: 0, c: [e.UTM] }, { cN: "params", b: /\(/, e: /\)/, k: a, relevance: 0, c: [e.ASM, e.QSM, e.CNM, e.CBCM] }, e.CLCM, e.CBCM] }, t, { cN: "meta", b: "@[A-Za-z]+" }] } }); hljs.registerLanguage("properties", function (e) { var r = "[ \\t\\f]*", t = "(" + r + "[:=]" + r + "|[ \\t\\f]+)", n = "([^\\\\\\W:= \\t\\f\\n]|\\\\.)+", a = "([^\\\\:= \\t\\f\\n]|\\\\.)+", c = { e: t, relevance: 0, starts: { cN: "string", e: /$/, relevance: 0, c: [{ b: "\\\\\\n" }] } }; return { cI: !0, i: /\S/, c: [e.C("^\\s*[!#]", "$"), { b: n + t, rB: !0, c: [{ cN: "attr", b: n, endsParent: !0, relevance: 0 }], starts: c }, { b: a + t, rB: !0, relevance: 0, c: [{ cN: "meta", b: a, endsParent: !0, relevance: 0 }], starts: c }, { cN: "attr", relevance: 0, b: a + r + "$" }] } }); hljs.registerLanguage("php", function (e) { var c = { b: "\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*" }, i = { cN: "meta", b: /<\?(php)?|\?>/ }, t = { cN: "string", c: [e.BE, i], v: [{ b: 'b"', e: '"' }, { b: "b'", e: "'" }, e.inherit(e.ASM, { i: null }), e.inherit(e.QSM, { i: null })] }, a = { v: [e.BNM, e.CNM] }; return { aliases: ["php", "php3", "php4", "php5", "php6", "php7"], cI: !0, k: "and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally", c: [e.HCM, e.C("//", "$", { c: [i] }), e.C("/\\*", "\\*/", { c: [{ cN: "doctag", b: "@[A-Za-z]+" }] }), e.C("__halt_compiler.+?;", !1, { eW: !0, k: "__halt_compiler", l: e.UIR }), { cN: "string", b: /<<<['"]?\w+['"]?$/, e: /^\w+;?$/, c: [e.BE, { cN: "subst", v: [{ b: /\$\w+/ }, { b: /\{\$/, e: /\}/ }] }] }, i, { cN: "keyword", b: /\$this\b/ }, c, { b: /(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/ }, { cN: "function", bK: "function", e: /[;{]/, eE: !0, i: "\\$|\\[|%", c: [e.UTM, { cN: "params", b: "\\(", e: "\\)", c: ["self", c, e.CBCM, t, a] }] }, { cN: "class", bK: "class interface", e: "{", eE: !0, i: /[:\(\$"]/, c: [{ bK: "extends implements" }, e.UTM] }, { bK: "namespace", e: ";", i: /[\.']/, c: [e.UTM] }, { bK: "use", e: ";", c: [e.UTM] }, { b: "=>" }, t, a] } }); hljs.registerLanguage("nginx", function (e) { var r = { cN: "variable", v: [{ b: /\$\d+/ }, { b: /\$\{/, e: /}/ }, { b: "[\\$\\@]" + e.UIR }] }, b = { eW: !0, l: "[a-z/_]+", k: { literal: "on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll" }, relevance: 0, i: "=>", c: [e.HCM, { cN: "string", c: [e.BE, r], v: [{ b: /"/, e: /"/ }, { b: /'/, e: /'/ }] }, { b: "([a-z]+):/", e: "\\s", eW: !0, eE: !0, c: [r] }, { cN: "regexp", c: [e.BE, r], v: [{ b: "\\s\\^", e: "\\s|{|;", rE: !0 }, { b: "~\\*?\\s+", e: "\\s|{|;", rE: !0 }, { b: "\\*(\\.[a-z\\-]+)+" }, { b: "([a-z\\-]+\\.)+\\*" }] }, { cN: "number", b: "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b" }, { cN: "number", b: "\\b\\d+[kKmMgGdshdwy]*\\b", relevance: 0 }, r] }; return { aliases: ["nginxconf"], c: [e.HCM, { b: e.UIR + "\\s+{", rB: !0, e: "{", c: [{ cN: "section", b: e.UIR }], relevance: 0 }, { b: e.UIR + "\\s", e: ";|{", rB: !0, c: [{ cN: "attribute", b: e.UIR, starts: b }], relevance: 0 }], i: "[^\\s\\}]" } }); hljs.registerLanguage("makefile", function (e) { var i = { cN: "variable", v: [{ b: "\\$\\(" + e.UIR + "\\)", c: [e.BE] }, { b: /\$[@%/, e: /$/, i: "\\n" }, e.CLCM, e.CBCM] }, { cN: "class", b: "(" + i.split(" ").join("|") + ")\\b", e: "({|$)", eE: !0, k: i, l: t, c: [e.UTM] }, { b: "\\." + e.UIR, relevance: 0 }] } }); hljs.registerLanguage("typescript", function (e) { var r = "[A-Za-z$_][0-9A-Za-z$_]*", t = { keyword: "in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await", literal: "true false null undefined NaN Infinity", built_in: "eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise" }, n = { cN: "meta", b: "@" + r }, a = { b: "\\(", e: /\)/, k: t, c: ["self", e.QSM, e.ASM, e.NM] }, c = { cN: "params", b: /\(/, e: /\)/, eB: !0, eE: !0, k: t, c: [e.CLCM, e.CBCM, n, a] }, s = { cN: "number", v: [{ b: "\\b(0[bB][01]+)n?" }, { b: "\\b(0[oO][0-7]+)n?" }, { b: e.CNR + "n?" }], relevance: 0 }, o = { cN: "subst", b: "\\$\\{", e: "\\}", k: t, c: [] }, i = { b: "html`", e: "", starts: { e: "`", rE: !1, c: [e.BE, o], sL: "xml" } }, l = { b: "css`", e: "", starts: { e: "`", rE: !1, c: [e.BE, o], sL: "css" } }, b = { cN: "string", b: "`", e: "`", c: [e.BE, o] }; return o.c = [e.ASM, e.QSM, i, l, b, s, e.RM], { aliases: ["ts"], k: t, c: [{ cN: "meta", b: /^\s*['"]use strict['"]/ }, e.ASM, e.QSM, i, l, b, e.CLCM, e.CBCM, s, { b: "(" + e.RSR + "|\\b(case|return|throw)\\b)\\s*", k: "return throw case", c: [e.CLCM, e.CBCM, e.RM, { cN: "function", b: "(\\(.*?\\)|" + e.IR + ")\\s*=>", rB: !0, e: "\\s*=>", c: [{ cN: "params", v: [{ b: e.IR }, { b: /\(\s*\)/ }, { b: /\(/, e: /\)/, eB: !0, eE: !0, k: t, c: ["self", e.CLCM, e.CBCM] }] }] }], relevance: 0 }, { cN: "function", bK: "function", e: /[\{;]/, eE: !0, k: t, c: ["self", e.inherit(e.TM, { b: r }), c], i: /%/, relevance: 0 }, { bK: "constructor", e: /[\{;]/, eE: !0, c: ["self", c] }, { b: /module\./, k: { built_in: "module" }, relevance: 0 }, { bK: "module", e: /\{/, eE: !0 }, { bK: "interface", e: /\{/, eE: !0, k: "interface extends" }, { b: /\$[(.]/ }, { b: "\\." + e.IR, relevance: 0 }, n, a] } }); hljs.registerLanguage("elm", function (e) { var i = { v: [e.C("--", "$"), e.C("{-", "-}", { c: ["self"] })] }, t = { cN: "type", b: "\\b[A-Z][\\w']*", relevance: 0 }, c = { b: "\\(", e: "\\)", i: '"', c: [{ cN: "type", b: "\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?" }, i] }; return { k: "let in if then else case of where module import exposing type alias as infix infixl infixr port effect command subscription", c: [{ bK: "port effect module", e: "exposing", k: "port effect module where command subscription exposing", c: [c, i], i: "\\W\\.|;" }, { b: "import", e: "$", k: "import as exposing", c: [c, i], i: "\\W\\.|;" }, { b: "type", e: "$", k: "type alias", c: [t, c, { b: "{", e: "}", c: c.c }, i] }, { bK: "infix infixl infixr", e: "$", c: [e.CNM, i] }, { b: "port", e: "$", k: "port", c: [i] }, { cN: "string", b: "'\\\\?.", e: "'", i: "." }, e.QSM, e.CNM, t, e.inherit(e.TM, { b: "^[_a-z][\\w']*" }), i, { b: "->|<-" }], i: /;/ } }); hljs.registerLanguage("http", function (e) { var t = "HTTP/[0-9\\.]+"; return { aliases: ["https"], i: "\\S", c: [{ b: "^" + t, e: "$", c: [{ cN: "number", b: "\\b\\d{3}\\b" }] }, { b: "^[A-Z]+ (.*?) " + t + "$", rB: !0, e: "$", c: [{ cN: "string", b: " ", e: " ", eB: !0, eE: !0 }, { b: t }, { cN: "keyword", b: "[A-Z]+" }] }, { cN: "attribute", b: "^\\w", e: ": ", eE: !0, i: "\\n|\\s|=", starts: { e: "$", relevance: 0 } }, { b: "\\n\\n", starts: { sL: [], eW: !0 } }] } }); hljs.registerLanguage("diff", function (e) { return { aliases: ["patch"], c: [{ cN: "meta", relevance: 10, v: [{ b: /^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/ }, { b: /^\*\*\* +\d+,\d+ +\*\*\*\*$/ }, { b: /^\-\-\- +\d+,\d+ +\-\-\-\-$/ }] }, { cN: "comment", v: [{ b: /Index: /, e: /$/ }, { b: /={3,}/, e: /$/ }, { b: /^\-{3}/, e: /$/ }, { b: /^\*{3} /, e: /$/ }, { b: /^\+{3}/, e: /$/ }, { b: /^\*{15}$/ }] }, { cN: "addition", b: "^\\+", e: "$" }, { cN: "deletion", b: "^\\-", e: "$" }, { cN: "addition", b: "^\\!", e: "$" }] } }); hljs.registerLanguage("lisp", function (e) { var b = "[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*", c = "\\|[^]*?\\|", a = "(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?", l = { cN: "literal", b: "\\b(t{1}|nil)\\b" }, n = { cN: "number", v: [{ b: a, relevance: 0 }, { b: "#(b|B)[0-1]+(/[0-1]+)?" }, { b: "#(o|O)[0-7]+(/[0-7]+)?" }, { b: "#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?" }, { b: "#(c|C)\\(" + a + " +" + a, e: "\\)" }] }, r = e.inherit(e.QSM, { i: null }), i = e.C(";", "$", { relevance: 0 }), t = { b: "\\*", e: "\\*" }, v = { cN: "symbol", b: "[:&]" + b }, s = { b: b, relevance: 0 }, u = { b: c }, d = { c: [n, r, t, v, { b: "\\(", e: "\\)", c: ["self", l, r, n, s] }, s], v: [{ b: "['`]\\(", e: "\\)" }, { b: "\\(quote ", e: "\\)", k: { name: "quote" } }, { b: "'" + c }] }, f = { v: [{ b: "'" + b }, { b: "#'" + b + "(::" + b + ")*" }] }, m = { b: "\\(\\s*", e: "\\)" }, o = { eW: !0, relevance: 0 }; return m.c = [{ cN: "name", v: [{ b: b }, { b: c }] }, o], o.c = [d, f, m, l, n, r, i, t, v, u, s], { i: /\S/, c: [n, { cN: "meta", b: "^#!", e: "$" }, l, r, i, d, f, m, s] } }); hljs.registerLanguage("kotlin", function (e) { var t = { keyword: "abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default", built_in: "Byte Short Char Int Long Boolean Float Double Void Unit Nothing", literal: "true false null" }, a = { cN: "symbol", b: e.UIR + "@" }, n = { cN: "subst", b: "\\${", e: "}", c: [e.CNM] }, c = { cN: "variable", b: "\\$" + e.UIR }, r = { cN: "string", v: [{ b: '"""', e: '"""(?=[^"])', c: [c, n] }, { b: "'", e: "'", i: /\n/, c: [e.BE] }, { b: '"', e: '"', i: /\n/, c: [e.BE, c, n] }] }; n.c.push(r); var i = { cN: "meta", b: "@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*" + e.UIR + ")?" }, l = { cN: "meta", b: "@" + e.UIR, c: [{ b: /\(/, e: /\)/, c: [e.inherit(r, { cN: "meta-string" })] }] }, s = { cN: "number", b: "\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?", relevance: 0 }, b = e.C("/\\*", "\\*/", { c: [e.CBCM] }), o = { v: [{ cN: "type", b: e.UIR }, { b: /\(/, e: /\)/, c: [] }] }, d = o; return d.v[1].c = [o], o.v[1].c = [d], { aliases: ["kt"], k: t, c: [e.C("/\\*\\*", "\\*/", { relevance: 0, c: [{ cN: "doctag", b: "@[A-Za-z]+" }] }), e.CLCM, b, { cN: "keyword", b: /\b(break|continue|return|this)\b/, starts: { c: [{ cN: "symbol", b: /@\w+/ }] } }, a, i, l, { cN: "function", bK: "fun", e: "[(]|$", rB: !0, eE: !0, k: t, i: /fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/, relevance: 5, c: [{ b: e.UIR + "\\s*\\(", rB: !0, relevance: 0, c: [e.UTM] }, { cN: "type", b: //, k: "reified", relevance: 0 }, { cN: "params", b: /\(/, e: /\)/, endsParent: !0, k: t, relevance: 0, c: [{ b: /:/, e: /[=,\/]/, eW: !0, c: [o, e.CLCM, b], relevance: 0 }, e.CLCM, b, i, l, r, e.CNM] }, b] }, { cN: "class", bK: "class interface trait", e: /[:\{(]|$/, eE: !0, i: "extends implements", c: [{ bK: "public protected internal private constructor" }, e.UTM, { cN: "type", b: //, eB: !0, eE: !0, relevance: 0 }, { cN: "type", b: /[,:]\s*/, e: /[<\(,]|$/, eB: !0, rE: !0 }, i, l] }, r, { cN: "meta", b: "^#!/usr/bin/env", e: "$", i: "\n" }, s] } }); hljs.registerLanguage("dart", function (e) { var t = { cN: "subst", v: [{ b: "\\$[A-Za-z0-9_]+" }] }, r = { cN: "subst", v: [{ b: "\\${", e: "}" }], k: "true false null this is new super" }, n = { cN: "string", v: [{ b: "r'''", e: "'''" }, { b: 'r"""', e: '"""' }, { b: "r'", e: "'", i: "\\n" }, { b: 'r"', e: '"', i: "\\n" }, { b: "'''", e: "'''", c: [e.BE, t, r] }, { b: '"""', e: '"""', c: [e.BE, t, r] }, { b: "'", e: "'", i: "\\n", c: [e.BE, t, r] }, { b: '"', e: '"', i: "\\n", c: [e.BE, t, r] }] }; r.c = [e.CNM, n]; return { k: { keyword: "abstract as assert async await break case catch class const continue covariant default deferred do dynamic else enum export extends extension external factory false final finally for Function get hide if implements import in inferface is library mixin new null on operator part rethrow return set show static super switch sync this throw true try typedef var void while with yield", built_in: "Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double dynamic int num print Element ElementList document querySelector querySelectorAll window" }, c: [n, e.C("/\\*\\*", "\\*/", { sL: "markdown" }), e.C("///+\\s*", "$", { c: [{ sL: "markdown", b: ".", e: "$" }] }), e.CLCM, e.CBCM, { cN: "class", bK: "class interface", e: "{", eE: !0, c: [{ bK: "extends implements" }, e.UTM] }, e.CNM, { cN: "meta", b: "@[A-Za-z]+" }, { b: "=>" }] } }); hljs.registerLanguage("applescript", function (e) { var t = e.inherit(e.QSM, { i: "" }), r = { cN: "params", b: "\\(", e: "\\)", c: ["self", e.CNM, t] }, i = e.C("--", "$"), o = [i, e.C("\\(\\*", "\\*\\)", { c: ["self", i] }), e.HCM]; return { aliases: ["osascript"], k: { keyword: "about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without", literal: "AppleScript false linefeed return pi quote result space tab true", built_in: "alias application boolean class constant date file integer list number real record string text activate beep count delay launch log offset read round run say summarize write character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year" }, c: [t, e.CNM, { cN: "built_in", b: "\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b" }, { cN: "literal", b: "\\b(text item delimiters|current application|missing value)\\b" }, { cN: "keyword", b: "\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference)|POSIX file|POSIX path|(date|time) string|quoted form)\\b" }, { bK: "on", i: "[${=;\\n]", c: [e.UTM, r] }].concat(o), i: "//|->|=>|\\[\\[" } }); hljs.registerLanguage("autohotkey", function (e) { var a = { b: "`[\\s\\S]" }; return { cI: !0, aliases: ["ahk"], k: { keyword: "Break Continue Critical Exit ExitApp Gosub Goto New OnExit Pause return SetBatchLines SetTimer Suspend Thread Throw Until ahk_id ahk_class ahk_pid ahk_exe ahk_group", literal: "true false NOT AND OR", built_in: "ComSpec Clipboard ClipboardAll ErrorLevel" }, c: [a, e.inherit(e.QSM, { c: [a] }), e.C(";", "$", { relevance: 0 }), e.CBCM, { cN: "number", b: e.NR, relevance: 0 }, { cN: "variable", b: "%[a-zA-Z0-9#_$@]+%" }, { cN: "built_in", b: "^\\s*\\w+\\s*(,|%)" }, { cN: "title", v: [{ b: '^[^\\n";]+::(?!=)' }, { b: '^[^\\n";]+:(?!=)', relevance: 0 }] }, { cN: "meta", b: "^\\s*#\\w+", e: "$", relevance: 0 }, { cN: "built_in", b: "A_[a-zA-Z0-9]+" }, { b: ",\\s*," }] } }); hljs.registerLanguage("rust", function (e) { var t = "([ui](8|16|32|64|128|size)|f(32|64))?", r = "drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"; return { aliases: ["rs"], k: { keyword: "abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield", literal: "true false Some None Ok Err", built_in: r }, l: e.IR + "!?", i: "" }] } }); hljs.registerLanguage("scss", function (e) { var t = "@[a-z-]+", r = { cN: "variable", b: "(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b" }, i = { cN: "number", b: "#[0-9A-Fa-f]+" }; e.CSSNM, e.QSM, e.ASM, e.CBCM; return { cI: !0, i: "[=/|']", c: [e.CLCM, e.CBCM, { cN: "selector-id", b: "\\#[A-Za-z0-9_-]+", relevance: 0 }, { cN: "selector-class", b: "\\.[A-Za-z0-9_-]+", relevance: 0 }, { cN: "selector-attr", b: "\\[", e: "\\]", i: "$" }, { cN: "selector-tag", b: "\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b", relevance: 0 }, { cN: "selector-pseudo", b: ":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)" }, { cN: "selector-pseudo", b: "::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)" }, r, { cN: "attribute", b: "\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b", i: "[^\\s]" }, { b: "\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" }, { b: ":", e: ";", c: [r, i, e.CSSNM, e.QSM, e.ASM, { cN: "meta", b: "!important" }] }, { b: "@(page|font-face)", l: t, k: "@page @font-face" }, { b: "@", e: "[{;]", rB: !0, k: "and or not only", c: [{ b: t, cN: "keyword" }, r, e.QSM, e.ASM, i, e.CSSNM] }] } }); hljs.registerLanguage("dockerfile", function (e) { return { aliases: ["docker"], cI: !0, k: "from maintainer expose env arg user onbuild stopsignal", c: [e.HCM, e.ASM, e.QSM, e.NM, { bK: "run cmd entrypoint volume add copy workdir label healthcheck shell", starts: { e: /[^\\]$/, sL: "bash" } }], i: "" }, { cN: "attribute", b: /\w+/, relevance: 0, k: { nomarkup: "order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername" }, starts: { e: /$/, relevance: 0, k: { literal: "on off all" }, c: [{ cN: "meta", b: "\\s\\[", e: "\\]$" }, { cN: "variable", b: "[\\$%]\\{", e: "\\}", c: ["self", r] }, r, e.QSM] } }], i: /\S/ } }); hljs.registerLanguage("prolog", function (e) { var c = { b: /\(/, e: /\)/, relevance: 0 }, b = { b: /\[/, e: /\]/ }, n = { cN: "comment", b: /%/, e: /$/, c: [e.PWM] }, r = { cN: "string", b: /`/, e: /`/, c: [e.BE] }, a = [{ b: /[a-z][A-Za-z0-9_]*/, relevance: 0 }, { cN: "symbol", v: [{ b: /[A-Z][a-zA-Z0-9_]*/ }, { b: /_[A-Za-z0-9_]*/ }], relevance: 0 }, c, { b: /:-/ }, b, n, e.CBCM, e.QSM, e.ASM, r, { cN: "string", b: /0\'(\\\'|.)/ }, { cN: "string", b: /0\'\\s/ }, e.CNM]; return c.c = a, { c: (b.c = a).concat([{ b: /\.$/ }]) } }); hljs.registerLanguage("matlab", function (e) { var a = "('|\\.')+", s = { relevance: 0, c: [{ b: a }] }; return { k: { keyword: "break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while", built_in: "sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson max min nanmax nanmin mean nanmean type table readtable writetable sortrows sort figure plot plot3 scatter scatter3 cellfun legend intersect ismember procrustes hold num2cell " }, i: '(//|"|#|/\\*|\\s+/\\w+)', c: [{ cN: "function", bK: "function", e: "$", c: [e.UTM, { cN: "params", v: [{ b: "\\(", e: "\\)" }, { b: "\\[", e: "\\]" }] }] }, { cN: "built_in", b: /true|false/, relevance: 0, starts: s }, { b: "[a-zA-Z][a-zA-Z_0-9]*" + a, relevance: 0 }, { cN: "number", b: e.CNR, relevance: 0, starts: s }, { cN: "string", b: "'", e: "'", c: [e.BE, { b: "''" }] }, { b: /\]|}|\)/, relevance: 0, starts: s }, { cN: "string", b: '"', e: '"', c: [e.BE, { b: '""' }], starts: s }, e.C("^\\s*\\%\\{\\s*$", "^\\s*\\%\\}\\s*$"), e.C("\\%", "$")] } }); hljs.registerLanguage("tcl", function (e) { return { aliases: ["tk"], k: "after append apply array auto_execok auto_import auto_load auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock close concat continue dde dict encoding eof error eval exec exit expr fblocked fconfigure fcopy file fileevent filename flush for foreach format gets glob global history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename return safe scan seek set socket source split string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update uplevel upvar variable vwait while", c: [e.C(";[ \\t]*#", "$"), e.C("^[ \\t]*#", "$"), { bK: "proc", e: "[\\{]", eE: !0, c: [{ cN: "title", b: "[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*", e: "[ \\t\\n\\r]", eW: !0, eE: !0 }] }, { eE: !0, v: [{ b: "\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*\\(([a-zA-Z0-9_])*\\)", e: "[^a-zA-Z0-9_\\}\\$]" }, { b: "\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*", e: "(\\))?[^a-zA-Z0-9_\\}\\$]" }] }, { cN: "string", c: [e.BE], v: [e.inherit(e.QSM, { i: null })] }, { cN: "number", v: [e.BNM, e.CNM] }] } }); hljs.registerLanguage("go", function (e) { var n = { keyword: "break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune", literal: "true false iota nil", built_in: "append cap close complex copy imag len make new panic print println real recover delete" }; return { aliases: ["golang"], k: n, i: "]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*" }, { b: "<%[%=-]?", e: "[%-]?%>", sL: "ruby", eB: !0, eE: !0, relevance: 0 }, { cN: "type", b: "!" + e.UIR }, { cN: "type", b: "!!" + e.UIR }, { cN: "meta", b: "&" + e.UIR + "$" }, { cN: "meta", b: "\\*" + e.UIR + "$" }, { cN: "bullet", b: "\\-(?=[ ]|$)", relevance: 0 }, e.HCM, { bK: b, k: { literal: b } }, { cN: "number", b: e.CNR + "\\b" }, a] } }); +// @license-end \ No newline at end of file From 2b21a5bb812275b718c88057dce9d3a10f2bd07e Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 17:11:05 -0700 Subject: [PATCH 101/233] Mobile: Change initial scale for better fit --- templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index 86c5a29..f90c0b0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,7 +4,7 @@ {% block title %}{{ sitename }}{% endblock %} - + From 2e0319b602f94b33a6acdef9261eb32f26c8a624 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 17:14:50 -0700 Subject: [PATCH 102/233] Fix project name in footer --- templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index f90c0b0..7e4f82d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -31,7 +31,7 @@ {% block content %}{% endblock %}
From 3167ab7ff08aa09b32d4aed892c7601a038f946f Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 12 Mar 2020 18:34:08 -0700 Subject: [PATCH 103/233] README: Update screenshots --- README.md | 117 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 98bf585..ff22db3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -linx-server +linx-server ====== [![Build Status](https://travis-ci.org/andreimarcu/linx-server.svg?branch=master)](https://travis-ci.org/andreimarcu/linx-server) @@ -19,14 +19,45 @@ You can see what it looks like using the demo: [https://demo.linx-server.net/](h ### Screenshots - + + + -Get release and run +Getting started ------------------- + +#### Using Docker +Example running +``` +docker run -p 8080:8080 -v /path/to/meta:/data/meta -v /path/to/files:/data/files andreimarcu/linx-server +``` + +Example with docker-compose +``` +version: '2.2' +services: + linx-server: + container_name: linx-server + image: andreimarcu/linx-server + entrypoint: /usr/local/bin/linx-server -bind=0.0.0.0:8080 -filespath=/data/files/ -metapath=/data/meta/ + command: -sitename=Linx -siteurl=https://linx.example.com + volumes: + - /path/to/files:/data/files + - /path/to/meta:/data/meta + network_mode: bridge + ports: + - "8080:8080" + restart: unless-stopped +``` +Ideally, you would use a reverse proxy such as nginx or caddy to handle TLS certificates. + +#### Using a binary release + 1. Grab the latest binary from the [releases](https://github.com/andreimarcu/linx-server/releases) 2. Run ```./linx-server``` + Usage ----- @@ -38,30 +69,36 @@ maxsize = 4294967296 allowhotlink = true # etc ``` -...and then invoke ```linx-server -config path/to/config.ini``` +...and then invoke ```linx-server -config path/to/config.ini``` #### Options -- ```-bind 127.0.0.1:8080``` -- what to bind to (default is 127.0.0.1:8080) -- ```-sitename myLinx``` -- the site name displayed on top (default is inferred from Host header) -- ```-siteurl "https://mylinx.example.org/"``` -- the site url (default is inferred from execution context) -- ```-selifpath "selif"``` -- path relative to site base url (the "selif" in mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) -- ```-maxsize 4294967296``` -- maximum upload file size in bytes (default 4GB) -- ```-maxexpiry 86400``` -- maximum expiration time in seconds (default is 0, which is no expiry) -- ```-allowhotlink``` -- Allow file hotlinking -- ```-contentsecuritypolicy "..."``` -- Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") -- ```-filecontentsecuritypolicy "..."``` -- Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") -- ```-refererpolicy "..."``` -- Referrer-Policy header for pages (default is "same-origin") -- ```-filereferrerpolicy "..."``` -- Referrer-Policy header for files (default is "same-origin") -- ```-xframeoptions "..." ``` -- X-Frame-Options header (default is "SAMEORIGIN") -- ```-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 + +|Option|Description +|------|----------- +| ```-bind 127.0.0.1:8080``` | what to bind to (default is 127.0.0.1:8080) +| ```-sitename myLinx``` | the site name displayed on top (default is inferred from Host header) +| ```-siteurl "https://mylinx.example.org/"``` | the site url (default is inferred from execution context) +| ```-selifpath "selif"``` | path relative to site base url (the "selif" in mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) +| ```-maxsize 4294967296``` | maximum upload file size in bytes (default 4GB) +| ```-maxexpiry 86400``` | maximum expiration time in seconds (default is 0, which is no expiry) +| ```-allowhotlink``` | Allow file hotlinking +| ```-contentsecuritypolicy "..."``` | Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") +| ```-filecontentsecuritypolicy "..."``` | Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") +| ```-refererpolicy "..."``` | Referrer-Policy header for pages (default is "same-origin") +| ```-filereferrerpolicy "..."``` | Referrer-Policy header for files (default is "same-origin") +| ```-xframeoptions "..." ``` | X-Frame-Options header (default is "SAMEORIGIN") +| ```-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 -- ```-remoteauthfile path/to/remoteauthfile``` -- (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys -- ```-basicauth``` -- (optionally) allow basic authorization to upload or paste files from browser when `-authfile` is enabled. When uploading, you will be prompted to enter a user and password - leave the user blank and use your auth key as the password + +|Option|Description +|------|----------- +| ```-authfile path/to/authfile``` | (optionally) require authorization for upload/delete by providing a newline-separated file of scrypted auth keys +| ```-remoteauthfile path/to/remoteauthfile``` | (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys +| ```-basicauth``` | (optionally) allow basic authorization to upload or paste files from browser when `-authfile` is enabled. When uploading, you will be prompted to enter a user and password - leave the user blank and use your auth key as the password A helper utility ```linx-genkey``` is provided which hashes keys to the format required in the auth files. @@ -75,14 +112,20 @@ The following storage backends are available: #### SSL with built-in server -- ```-certfile path/to/your.crt``` -- Path to the ssl certificate (required if you want to use the https server) -- ```-keyfile path/to/your.key``` -- Path to the ssl key (required if you want to use the https server) +|Option|Description +|------|----------- +| ```-certfile path/to/your.crt``` | Path to the ssl certificate (required if you want to use the https server) +| ```-keyfile path/to/your.key``` | Path to the ssl key (required if you want to use the https server) #### Use with http proxy -- ```-realip``` -- let linx-server know you (nginx, etc) are providing the X-Real-IP and/or X-Forwarded-For headers. +|Option|Description +|------|----------- +| ```-realip``` | let linx-server know you (nginx, etc) are providing the X-Real-IP and/or X-Forwarded-For headers. #### Use with fastcgi -- ```-fastcgi``` -- serve through fastcgi +|Option|Description +|------|----------- +| ```-fastcgi``` | serve through fastcgi Cleaning up expired files @@ -96,10 +139,12 @@ of scheduled task. You should be careful to ensure that only one instance of `linx-client` runs at a time to avoid unexpected behavior. It does not implement any type of locking. -#### Options -- ```-filespath files/``` -- Path to stored uploads (default is files/) -- ```-metapath meta/``` -- Path to stored information about uploads (default is meta/) -- ```-nologs``` -- (optionally) disable deletion logs in stdout + +|Option|Description +|------|----------- +| ```-filespath files/``` | Path to stored uploads (default is files/) +| ```-nologs``` | (optionally) disable deletion logs in stdout +| ```-metapath meta/``` | Path to stored information about uploads (default is meta/) Deployment @@ -133,16 +178,6 @@ Run linx-server with the ```-certfile path/to/cert.file``` and ```-keyfile path/ #### 3. Using the built-in http server Run linx-server normally. -#### 4. Using Docker with the built-in http server -First, build the image: -```docker build -t linx-server .``` - -You'll need some directories for the persistent storage. For the purposes of this example, we will use `/media/meta` and `/media/files`. - -Then, run it: -```docker run -p 8080:8080 -v /media/meta:/data/meta -v /media/files:/data/files linx-server``` - - Development ----------- Any help is welcome, PRs will be reviewed and merged accordingly. From 9f7e6b6eea01ef3fa19cbcfdfb952021ca6aee53 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Wed, 18 Mar 2020 17:26:43 -0700 Subject: [PATCH 104/233] Torrent: Ranged requests so torrents work --- backends/localfs/localfs.go | 13 +++++++++++++ backends/s3/s3.go | 38 +++++++++++++++++++++++++++++++++++++ backends/storage.go | 2 ++ fileserve.go | 11 +++-------- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/backends/localfs/localfs.go b/backends/localfs/localfs.go index 42e32b8..aaf487f 100644 --- a/backends/localfs/localfs.go +++ b/backends/localfs/localfs.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "io/ioutil" + "net/http" "os" "path" "time" @@ -82,6 +83,18 @@ func (b LocalfsBackend) Get(key string) (metadata backends.Metadata, f io.ReadCl return } +func (b LocalfsBackend) ServeFile(key string, w http.ResponseWriter, r *http.Request) (err error) { + _, err = b.Head(key) + if err != nil { + return + } + + filePath := path.Join(b.filesPath, key) + http.ServeFile(w, r, filePath) + + return +} + func (b LocalfsBackend) writeMetadata(key string, metadata backends.Metadata) error { metaPath := path.Join(b.metaPath, key) diff --git a/backends/s3/s3.go b/backends/s3/s3.go index bfc6e1c..a558779 100644 --- a/backends/s3/s3.go +++ b/backends/s3/s3.go @@ -3,6 +3,7 @@ package s3 import ( "io" "io/ioutil" + "net/http" "os" "strconv" "time" @@ -79,6 +80,43 @@ func (b S3Backend) Get(key string) (metadata backends.Metadata, r io.ReadCloser, return } +func (b S3Backend) ServeFile(key string, w http.ResponseWriter, r *http.Request) (err error) { + var result *s3.GetObjectOutput + + if r.Header.Get("Range") != "" { + result, err = b.svc.GetObject(&s3.GetObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + Range: aws.String(r.Header.Get("Range")), + }) + + w.WriteHeader(206) + w.Header().Set("Content-Range", *result.ContentRange) + w.Header().Set("Content-Length", strconv.FormatInt(*result.ContentLength, 10)) + w.Header().Set("Accept-Ranges", "bytes") + + } else { + result, err = b.svc.GetObject(&s3.GetObjectInput{ + Bucket: aws.String(b.bucket), + Key: aws.String(key), + }) + + } + + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + if aerr.Code() == s3.ErrCodeNoSuchKey || aerr.Code() == "NotFound" { + err = backends.NotFoundErr + } + } + return + } + + _, err = io.Copy(w, result.Body) + + return +} + func mapMetadata(m backends.Metadata) map[string]*string { return map[string]*string{ "Expiry": aws.String(strconv.FormatInt(m.Expiry.Unix(), 10)), diff --git a/backends/storage.go b/backends/storage.go index 5d973c4..864d0a1 100644 --- a/backends/storage.go +++ b/backends/storage.go @@ -3,6 +3,7 @@ package backends import ( "errors" "io" + "net/http" "time" ) @@ -13,6 +14,7 @@ type StorageBackend interface { Get(key string) (Metadata, io.ReadCloser, error) Put(key string, r io.Reader, expiry time.Time, deleteKey, accessKey string) (Metadata, error) PutMetadata(key string, m Metadata) error + ServeFile(key string, w http.ResponseWriter, r *http.Request) error Size(key string) (int64, error) } diff --git a/fileserve.go b/fileserve.go index 27a28a9..90c1507 100644 --- a/fileserve.go +++ b/fileserve.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io" "net/http" "net/url" "strconv" @@ -61,15 +60,11 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) { } if r.Method != "HEAD" { - _, reader, err := storageBackend.Get(fileName) - if err != nil { - oopsHandler(c, w, r, RespAUTO, "Unable to open file.") - return - } - defer reader.Close() - if _, err = io.CopyN(w, reader, metadata.Size); err != nil { + storageBackend.ServeFile(fileName, w, r) + if err != nil { oopsHandler(c, w, r, RespAUTO, err.Error()) + return } } } From 8edf53c14203bca15b39a9778abe23e14d707a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 13 Apr 2020 21:36:03 +0200 Subject: [PATCH 105/233] Add systemd unit/timer for the cleanup tool (#210) * add systemd service for the cleanup script * add systemd timer for the cleanup service --- linx-cleanup.service | 12 ++++++++++++ linx-cleanup.timer | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 linx-cleanup.service create mode 100644 linx-cleanup.timer diff --git a/linx-cleanup.service b/linx-cleanup.service new file mode 100644 index 0000000..7a03941 --- /dev/null +++ b/linx-cleanup.service @@ -0,0 +1,12 @@ +[Unit] +Description=Self-hosted file/code/media sharing (expired files cleanup) +After=network.target + +[Service] +User=linx +Group=linx +ExecStart=/usr/bin/linx-cleanup +WorkingDirectory=/srv/linx/ + +[Install] +WantedBy=multi-user.target diff --git a/linx-cleanup.timer b/linx-cleanup.timer new file mode 100644 index 0000000..40d8cda --- /dev/null +++ b/linx-cleanup.timer @@ -0,0 +1,8 @@ +[Unit] +Description=Run linx-cleanup every hour + +[Timer] +OnUnitActiveSec=1h + +[Install] +WantedBy=timers.target From bc599ae0188b428dd07cc9e0dc6a1ce53c44dd57 Mon Sep 17 00:00:00 2001 From: mutantmonkey <67266+mutantmonkey@users.noreply.github.com> Date: Thu, 16 Apr 2020 21:18:21 +0000 Subject: [PATCH 106/233] Fix typo in readme (#213) This should say linx-cleanup, not linx-client. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff22db3..c7e0adf 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ automatically clean up files that have expired, you can use the included `linx-cleanup` utility. To run it automatically, use a cronjob or similar type of scheduled task. -You should be careful to ensure that only one instance of `linx-client` runs at +You should be careful to ensure that only one instance of `linx-cleanup` runs at a time to avoid unexpected behavior. It does not implement any type of locking. From 151515f516c615c15f418c3753c400db21a4bd12 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Wed, 13 May 2020 17:37:33 -0700 Subject: [PATCH 107/233] Cleanup: Integrate expired files periodic cleanup --- .gitignore | 4 +-- README.md | 33 +++++++------------ {linx-cleanup => cleanup}/cleanup.go | 26 +++++++-------- linx-cleanup/README.md | 19 +++++++++++ linx-cleanup/linx-cleanup.go | 23 +++++++++++++ .../linx-cleanup.service | 0 .../linx-cleanup.timer | 0 server.go | 8 +++++ 8 files changed, 75 insertions(+), 38 deletions(-) rename {linx-cleanup => cleanup}/cleanup.go (63%) create mode 100644 linx-cleanup/README.md create mode 100644 linx-cleanup/linx-cleanup.go rename linx-cleanup.service => linx-cleanup/linx-cleanup.service (100%) rename linx-cleanup.timer => linx-cleanup/linx-cleanup.timer (100%) diff --git a/.gitignore b/.gitignore index c6750eb..ec613f1 100644 --- a/.gitignore +++ b/.gitignore @@ -29,8 +29,8 @@ _testmain.go *.prof linx-server -linx-cleanup -linx-genkey +linx-cleanup/linx-cleanup +linx-genkey/linx-genkey files/ meta/ binaries/ diff --git a/README.md b/README.md index c7e0adf..567d646 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,17 @@ allowhotlink = true | ```-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 + +#### Cleaning up expired files +When files expire, access is disabled immediately, but the files and metadata +will persist on disk until someone attempts to access them. You can set the following option to run cleanup every few minutes. This can also be done using a separate utility found the linx-cleanup directory. + + +|Option|Description +|------|----------- +| ```-cleanup-every-minutes 5``` | How often to clean up expired files in minutes (default is 0, which means files will be cleaned up as they are accessed) + + #### Require API Keys for uploads |Option|Description @@ -127,26 +138,6 @@ The following storage backends are available: |------|----------- | ```-fastcgi``` | serve through fastcgi - -Cleaning up expired files -------------------------- -When files expire, access is disabled immediately, but the files and metadata -will persist on disk until someone attempts to access them. If you'd like to -automatically clean up files that have expired, you can use the included -`linx-cleanup` utility. To run it automatically, use a cronjob or similar type -of scheduled task. - -You should be careful to ensure that only one instance of `linx-cleanup` runs at -a time to avoid unexpected behavior. It does not implement any type of locking. - - -|Option|Description -|------|----------- -| ```-filespath files/``` | Path to stored uploads (default is files/) -| ```-nologs``` | (optionally) disable deletion logs in stdout -| ```-metapath meta/``` | Path to stored information about uploads (default is meta/) - - Deployment ---------- Linx-server supports being deployed in a subdirectory (ie. example.com/mylinx/) as well as on its own (example.com/). @@ -207,4 +198,4 @@ along with this program. If not, see . Author ------- -Andrei Marcu, http://andreim.net/ +Andrei Marcu, https://andreim.net/ diff --git a/linx-cleanup/cleanup.go b/cleanup/cleanup.go similarity index 63% rename from linx-cleanup/cleanup.go rename to cleanup/cleanup.go index 88c2bce..5920c22 100644 --- a/linx-cleanup/cleanup.go +++ b/cleanup/cleanup.go @@ -1,26 +1,14 @@ -package main +package cleanup import ( - "flag" "log" + "time" "github.com/andreimarcu/linx-server/backends/localfs" "github.com/andreimarcu/linx-server/expiry" ) -func main() { - var filesDir string - var metaDir string - var noLogs bool - - flag.StringVar(&filesDir, "filespath", "files/", - "path to files directory") - flag.StringVar(&metaDir, "metapath", "meta/", - "path to metadata directory") - flag.BoolVar(&noLogs, "nologs", false, - "don't log deleted files") - flag.Parse() - +func Cleanup(filesDir string, metaDir string, noLogs bool) { fileBackend := localfs.NewLocalfsBackend(metaDir, filesDir) files, err := fileBackend.List() @@ -44,3 +32,11 @@ func main() { } } } + +func PeriodicCleanup(minutes time.Duration, filesDir string, metaDir string, noLogs bool) { + c := time.Tick(minutes) + for range c { + Cleanup(filesDir, metaDir, noLogs) + } + +} diff --git a/linx-cleanup/README.md b/linx-cleanup/README.md new file mode 100644 index 0000000..7d4f4a3 --- /dev/null +++ b/linx-cleanup/README.md @@ -0,0 +1,19 @@ + +linx-cleanup +------------------------- +When files expire, access is disabled immediately, but the files and metadata +will persist on disk until someone attempts to access them. + +If you'd like to automatically clean up files that have expired, you can use the included `linx-cleanup` utility. To run it automatically, use a cronjob or similar type +of scheduled task. + +You should be careful to ensure that only one instance of `linx-cleanup` runs at +a time to avoid unexpected behavior. It does not implement any type of locking. + + +|Option|Description +|------|----------- +| ```-filespath files/``` | Path to stored uploads (default is files/) +| ```-nologs``` | (optionally) disable deletion logs in stdout +| ```-metapath meta/``` | Path to stored information about uploads (default is meta/) + diff --git a/linx-cleanup/linx-cleanup.go b/linx-cleanup/linx-cleanup.go new file mode 100644 index 0000000..13b3ef1 --- /dev/null +++ b/linx-cleanup/linx-cleanup.go @@ -0,0 +1,23 @@ +package main + +import ( + "flag" + + "github.com/andreimarcu/linx-server/cleanup" +) + +func main() { + var filesDir string + var metaDir string + var noLogs bool + + flag.StringVar(&filesDir, "filespath", "files/", + "path to files directory") + flag.StringVar(&metaDir, "metapath", "meta/", + "path to metadata directory") + flag.BoolVar(&noLogs, "nologs", false, + "don't log deleted files") + flag.Parse() + + cleanup.Cleanup(filesDir, metaDir, noLogs) +} diff --git a/linx-cleanup.service b/linx-cleanup/linx-cleanup.service similarity index 100% rename from linx-cleanup.service rename to linx-cleanup/linx-cleanup.service diff --git a/linx-cleanup.timer b/linx-cleanup/linx-cleanup.timer similarity index 100% rename from linx-cleanup.timer rename to linx-cleanup/linx-cleanup.timer diff --git a/server.go b/server.go index e1f0c0a..dae3491 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,7 @@ import ( "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/backends/localfs" "github.com/andreimarcu/linx-server/backends/s3" + "github.com/andreimarcu/linx-server/cleanup" "github.com/flosch/pongo2" "github.com/vharitonsky/iniflags" "github.com/zenazn/goji/graceful" @@ -71,6 +72,7 @@ var Config struct { forceRandomFilename bool accessKeyCookieExpiry uint64 customPagesDir string + cleanupEveryMinutes uint64 } var Templates = make(map[string]*pongo2.Template) @@ -150,6 +152,10 @@ func setup() *web.Mux { storageBackend = s3.NewS3Backend(Config.s3Bucket, Config.s3Region, Config.s3Endpoint, Config.s3ForcePathStyle) } else { storageBackend = localfs.NewLocalfsBackend(Config.metaDir, Config.filesDir) + if Config.cleanupEveryMinutes > 0 { + go cleanup.PeriodicCleanup(time.Duration(Config.cleanupEveryMinutes)*time.Minute, Config.filesDir, Config.metaDir, Config.noLogs) + } + } // Template setup @@ -311,6 +317,8 @@ func main() { 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") + flag.Uint64Var(&Config.cleanupEveryMinutes, "cleanup-every-minutes", 0, + "How often to clean up expired files in minutes (default is 0, which means files will be cleaned up as they are accessed)") iniflags.Parse() From e2a65a5b62a42f94d985ca7ffa3fa5e0d3673199 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 14 May 2020 00:51:19 -0700 Subject: [PATCH 108/233] README: Clarify docker usage and example --- .gitignore | 1 + README.md | 81 ++++++++++++++++++++++------------------ linx-server.conf.example | 12 ++++++ 3 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 linx-server.conf.example diff --git a/.gitignore b/.gitignore index ec613f1..df2bae9 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ _testmain.go linx-server linx-cleanup/linx-cleanup linx-genkey/linx-genkey +linx-server.conf files/ meta/ binaries/ diff --git a/README.md b/README.md index 567d646..d509689 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,14 @@ Getting started ------------------- #### Using Docker +1. Create directories ```files``` and ```meta``` and run ```chown -R 65534:65534 meta && chown -R 65534:65534 files``` +2. Create a config file (example provided in repo), we'll refer to it as __linx-server.conf__ in the following examples + + + Example running ``` -docker run -p 8080:8080 -v /path/to/meta:/data/meta -v /path/to/files:/data/files andreimarcu/linx-server +docker run -p 8080:8080 -v /path/to/linx-server.conf:/data/linx-server.conf -v /path/to/meta:/data/meta -v /path/to/files:/data/files andreimarcu/linx-server -config /data/linx-server.conf ``` Example with docker-compose @@ -40,11 +45,12 @@ services: linx-server: container_name: linx-server image: andreimarcu/linx-server - entrypoint: /usr/local/bin/linx-server -bind=0.0.0.0:8080 -filespath=/data/files/ -metapath=/data/meta/ - command: -sitename=Linx -siteurl=https://linx.example.com + entrypoint: /usr/local/bin/linx-server + command: -config /data/linx-server.conf volumes: - /path/to/files:/data/files - /path/to/meta:/data/meta + - /path/to/linx-server.conf:/data/linx-server.conf network_mode: bridge ports: - "8080:8080" @@ -57,40 +63,41 @@ Ideally, you would use a reverse proxy such as nginx or caddy to handle TLS cert 1. Grab the latest binary from the [releases](https://github.com/andreimarcu/linx-server/releases) 2. Run ```./linx-server``` - Usage ----- #### Configuration -All configuration options are accepted either as arguments or can be placed in an ini-style file as such: +All configuration options are accepted either as arguments or can be placed in a file as such (see example file linx-server.conf.example in repo): ```ini +bind = 127.0.0.1:8080 +sitename = myLinx maxsize = 4294967296 -allowhotlink = true -# etc -``` -...and then invoke ```linx-server -config path/to/config.ini``` +maxexpiry = 86400 +# ... etc +``` +...and then run ```linx-server -config path/to/linx-server.conf``` #### Options |Option|Description |------|----------- -| ```-bind 127.0.0.1:8080``` | what to bind to (default is 127.0.0.1:8080) -| ```-sitename myLinx``` | the site name displayed on top (default is inferred from Host header) -| ```-siteurl "https://mylinx.example.org/"``` | the site url (default is inferred from execution context) -| ```-selifpath "selif"``` | path relative to site base url (the "selif" in mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) -| ```-maxsize 4294967296``` | maximum upload file size in bytes (default 4GB) -| ```-maxexpiry 86400``` | maximum expiration time in seconds (default is 0, which is no expiry) -| ```-allowhotlink``` | Allow file hotlinking -| ```-contentsecuritypolicy "..."``` | Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") -| ```-filecontentsecuritypolicy "..."``` | Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") -| ```-refererpolicy "..."``` | Referrer-Policy header for pages (default is "same-origin") -| ```-filereferrerpolicy "..."``` | Referrer-Policy header for files (default is "same-origin") -| ```-xframeoptions "..." ``` | X-Frame-Options header (default is "SAMEORIGIN") -| ```-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 +| ```bind = 127.0.0.1:8080``` | what to bind to (default is 127.0.0.1:8080) +| ```sitename = myLinx``` | the site name displayed on top (default is inferred from Host header) +| ```siteurl = https://mylinx.example.org/``` | the site url (default is inferred from execution context) +| ```selifpath = selif``` | path relative to site base url (the "selif" in mylinx.example.org/selif/image.jpg) where files are accessed directly (default: selif) +| ```maxsize = 4294967296``` | maximum upload file size in bytes (default 4GB) +| ```maxexpiry = 86400``` | maximum expiration time in seconds (default is 0, which is no expiry) +| ```allowhotlink = true``` | Allow file hotlinking +| ```contentsecuritypolicy = "..."``` | Content-Security-Policy header for pages (default is "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") +| ```filecontentsecuritypolicy = "..."``` | Content-Security-Policy header for files (default is "default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';") +| ```refererpolicy = "..."``` | Referrer-Policy header for pages (default is "same-origin") +| ```filereferrerpolicy = "..."``` | Referrer-Policy header for files (default is "same-origin") +| ```xframeoptions = "..." ``` | X-Frame-Options header (default is "SAMEORIGIN") +| ```remoteuploads = true``` | (optionally) enable remote uploads (/upload?url=https://...) +| ```nologs = true``` | (optionally) disable request logs in stdout +| ```force-random-filename = true``` | (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 #### Cleaning up expired files @@ -100,16 +107,16 @@ will persist on disk until someone attempts to access them. You can set the foll |Option|Description |------|----------- -| ```-cleanup-every-minutes 5``` | How often to clean up expired files in minutes (default is 0, which means files will be cleaned up as they are accessed) +| ```cleanup-every-minutes = 5``` | How often to clean up expired files in minutes (default is 0, which means files will be cleaned up as they are accessed) #### Require API Keys for uploads |Option|Description |------|----------- -| ```-authfile path/to/authfile``` | (optionally) require authorization for upload/delete by providing a newline-separated file of scrypted auth keys -| ```-remoteauthfile path/to/remoteauthfile``` | (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys -| ```-basicauth``` | (optionally) allow basic authorization to upload or paste files from browser when `-authfile` is enabled. When uploading, you will be prompted to enter a user and password - leave the user blank and use your auth key as the password +| ```authfile = path/to/authfile``` | (optionally) require authorization for upload/delete by providing a newline-separated file of scrypted auth keys +| ```remoteauthfile = path/to/remoteauthfile``` | (optionally) require authorization for remote uploads by providing a newline-separated file of scrypted auth keys +| ```basicauth = true``` | (optionally) allow basic authorization to upload or paste files from browser when `-authfile` is enabled. When uploading, you will be prompted to enter a user and password - leave the user blank and use your auth key as the password A helper utility ```linx-genkey``` is provided which hashes keys to the format required in the auth files. @@ -118,25 +125,25 @@ The following storage backends are available: |Name|Notes|Options |----|-----|------- -|LocalFS|Enabled by default, this backend uses the filesystem|```-filespath files/``` -- Path to store uploads (default is files/)
```-metapath meta/``` -- Path to store information about uploads (default is meta/)| -|S3|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).|```-s3-endpoint https://...``` -- S3 endpoint
```-s3-region us-east-1``` -- S3 region
```-s3-bucket mybucket``` -- S3 bucket to use for files and metadata
```-s3-force-path-style``` (optional) -- force path-style addresing (e.g. https://s3.amazonaws.com/linx/example.txt)

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token| +|LocalFS|Enabled by default, this backend uses the filesystem|```filespath = files/``` -- Path to store uploads (default is files/)
```metapath = meta/``` -- Path to store information about uploads (default is meta/)| +|S3|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).|```s3-endpoint = https://...``` -- S3 endpoint
```s3-region = us-east-1``` -- S3 region
```s3-bucket = mybucket``` -- S3 bucket to use for files and metadata
```s3-force-path-style = true``` (optional) -- force path-style addresing (e.g. https://s3.amazonaws.com/linx/example.txt)

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token| #### SSL with built-in server |Option|Description |------|----------- -| ```-certfile path/to/your.crt``` | Path to the ssl certificate (required if you want to use the https server) -| ```-keyfile path/to/your.key``` | Path to the ssl key (required if you want to use the https server) +| ```certfile = path/to/your.crt``` | Path to the ssl certificate (required if you want to use the https server) +| ```keyfile = path/to/your.key``` | Path to the ssl key (required if you want to use the https server) #### Use with http proxy |Option|Description |------|----------- -| ```-realip``` | let linx-server know you (nginx, etc) are providing the X-Real-IP and/or X-Forwarded-For headers. +| ```realip = true``` | let linx-server know you (nginx, etc) are providing the X-Real-IP and/or X-Forwarded-For headers. #### Use with fastcgi |Option|Description |------|----------- -| ```-fastcgi``` | serve through fastcgi +| ```fastcgi = true``` | serve through fastcgi Deployment ---------- @@ -161,10 +168,10 @@ server { } } ``` -And run linx-server with the ```-fastcgi``` option. +And run linx-server with the ```fastcgi = true``` option. #### 2. Using the built-in https server -Run linx-server with the ```-certfile path/to/cert.file``` and ```-keyfile path/to/key.file``` options. +Run linx-server with the ```certfile = path/to/cert.file``` and ```keyfile = path/to/key.file``` options. #### 3. Using the built-in http server Run linx-server normally. diff --git a/linx-server.conf.example b/linx-server.conf.example new file mode 100644 index 0000000..eb2e1f8 --- /dev/null +++ b/linx-server.conf.example @@ -0,0 +1,12 @@ + +bind = 127.0.0.1:8080 +sitename = myLinx +siteurl = https://mylinx.example.org/ +selifpath = s +maxsize = 4294967296 +maxexpiry = 86400 +allowhotlink = true +remoteuploads = true +nologs = true +force-random-filename = false +cleanup-every-minutes = 5 \ No newline at end of file From 6ce2bd6b9f0685bf5d11e24c6a012258f618ae32 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Thu, 14 May 2020 01:12:24 -0700 Subject: [PATCH 109/233] Display pages: Add OpenGraph tags for media --- display.go | 1 + templates/display/audio.html | 7 +++++-- templates/display/image.html | 6 +++++- templates/display/video.html | 8 ++++++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/display.go b/display.go index 6ac87d6..6228216 100644 --- a/display.go +++ b/display.go @@ -122,6 +122,7 @@ func fileDisplayHandler(c web.C, w http.ResponseWriter, r *http.Request, fileNam "forcerandom": Config.forceRandomFilename, "lines": lines, "files": metadata.ArchiveFiles, + "siteurl": strings.TrimSuffix(getSiteURL(r), "/"), }, r, w) if err != nil { diff --git a/templates/display/audio.html b/templates/display/audio.html index b5ae1e3..ba491f0 100644 --- a/templates/display/audio.html +++ b/templates/display/audio.html @@ -1,9 +1,12 @@ {% extends "base.html" %} +{% block head %} + +{% endblock %} + {% block main %} -{% endblock %} - +{% endblock %} \ No newline at end of file diff --git a/templates/display/image.html b/templates/display/image.html index 807b7ad..985c504 100644 --- a/templates/display/image.html +++ b/templates/display/image.html @@ -1,7 +1,11 @@ {% extends "base.html" %} +{% block head %} + +{% endblock %} + {% block main %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/display/video.html b/templates/display/video.html index 317664b..54a796f 100644 --- a/templates/display/video.html +++ b/templates/display/video.html @@ -1,8 +1,12 @@ {% extends "base.html" %} +{% block head %} + +{% endblock %} + {% block main %} -{% endblock %} +{% endblock %} \ No newline at end of file From 5eb6f32ff0a0252caab5fbc8e154912b4b5e4676 Mon Sep 17 00:00:00 2001 From: Infinoid Date: Mon, 3 Aug 2020 01:16:47 -0400 Subject: [PATCH 110/233] Switch to a more comprehensive mimetype detection library (#231) --- go.mod | 2 +- go.sum | 4 ++-- helpers/helpers.go | 17 ++++----------- helpers/helpers_test.go | 46 ++++++++++++++++++++++++++++++++++++++++- upload.go | 8 +++---- 5 files changed, 56 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 50a48ab..f433699 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 github.com/dustin/go-humanize v1.0.0 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 + github.com/gabriel-vasile/mimetype v1.1.1 github.com/microcosm-cc/bluemonday v1.0.2 github.com/minio/sha256-simd v0.1.1 github.com/russross/blackfriday v1.5.1 @@ -15,5 +16,4 @@ require ( github.com/zeebo/bencode v1.0.0 github.com/zenazn/goji v0.9.0 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 - gopkg.in/h2non/filetype.v1 v1.0.5 ) diff --git a/go.sum b/go.sum index 15a736c..99d63bf 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 h1:GY1+t5Dr9OKADM64SYnQjw/w99HMYvQ0A8/JoUkxVmc= github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= +github.com/gabriel-vasile/mimetype v1.1.1 h1:qbN9MPuRf3bstHu9zkI9jDWNfH//9+9kHxr9oRBBBOA= +github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -68,8 +70,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/h2non/filetype.v1 v1.0.5 h1:CC1jjJjoEhNVbMhXYalmGBhOBK2V70Q1N850wt/98/Y= -gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/helpers/helpers.go b/helpers/helpers.go index f51d998..f13e302 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -7,8 +7,8 @@ import ( "unicode" "github.com/andreimarcu/linx-server/backends" + "github.com/gabriel-vasile/mimetype" "github.com/minio/sha256-simd" - "gopkg.in/h2non/filetype.v1" ) func GenerateMetadata(r io.Reader) (m backends.Metadata, err error) { @@ -21,7 +21,7 @@ func GenerateMetadata(r io.Reader) (m backends.Metadata, err error) { // Get first 512 bytes for mimetype detection header := make([]byte, 512) - _, err = teeReader.Read(header) + headerlen, err := teeReader.Read(header) if err != nil { return } @@ -47,17 +47,8 @@ func GenerateMetadata(r io.Reader) (m backends.Metadata, err error) { // Use the bytes we extracted earlier and attempt to determine the file // type - kind, err := filetype.Match(header) - if err != nil { - m.Mimetype = "application/octet-stream" - return m, err - } else if kind.MIME.Value != "" { - m.Mimetype = kind.MIME.Value - } else if printable(header) { - m.Mimetype = "text/plain" - } else { - m.Mimetype = "application/octet-stream" - } + kind := mimetype.Detect(header[:headerlen]) + m.Mimetype = kind.String() return } diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index 800d0d2..d891173 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -1,8 +1,10 @@ package helpers import ( + "bytes" "strings" "testing" + "unicode/utf16" ) func TestGenerateMetadata(t *testing.T) { @@ -17,7 +19,7 @@ func TestGenerateMetadata(t *testing.T) { t.Fatalf("Sha256sum was %q instead of expected value of %q", m.Sha256sum, expectedSha256sum) } - expectedMimetype := "text/plain" + expectedMimetype := "text/plain; charset=utf-8" if m.Mimetype != expectedMimetype { t.Fatalf("Mimetype was %q instead of expected value of %q", m.Mimetype, expectedMimetype) } @@ -27,3 +29,45 @@ func TestGenerateMetadata(t *testing.T) { t.Fatalf("Size was %d instead of expected value of %d", m.Size, expectedSize) } } + +func TestTextCharsets(t *testing.T) { + // verify that different text encodings are detected and passed through + orig := "This is a text string" + utf16 := utf16.Encode([]rune(orig)) + utf16LE := make([]byte, len(utf16)*2+2) + utf16BE := make([]byte, len(utf16)*2+2) + utf8 := []byte(orig) + utf16LE[0] = 0xff + utf16LE[1] = 0xfe + utf16BE[0] = 0xfe + utf16BE[1] = 0xff + for i := 0; i < len(utf16); i++ { + lsb := utf16[i] & 0xff + msb := utf16[i] >> 8 + utf16LE[i*2+2] = byte(lsb) + utf16LE[i*2+3] = byte(msb) + utf16BE[i*2+2] = byte(msb) + utf16BE[i*2+3] = byte(lsb) + } + + testcases := []struct { + data []byte + extension string + mimetype string + }{ + {mimetype: "text/plain; charset=utf-8", data: utf8}, + {mimetype: "text/plain; charset=utf-16le", data: utf16LE}, + {mimetype: "text/plain; charset=utf-16be", data: utf16BE}, + } + + for i, testcase := range testcases { + r := bytes.NewReader(testcase.data) + m, err := GenerateMetadata(r) + if err != nil { + t.Fatalf("[%d] unexpected error return %v\n", i, err) + } + if m.Mimetype != testcase.mimetype { + t.Errorf("[%d] Expected mimetype '%s', got mimetype '%s'\n", i, testcase.mimetype, m.Mimetype) + } + } +} diff --git a/upload.go b/upload.go index 8526260..3cac122 100644 --- a/upload.go +++ b/upload.go @@ -18,8 +18,8 @@ import ( "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/expiry" "github.com/dchest/uniuri" + "github.com/gabriel-vasile/mimetype" "github.com/zenazn/goji/web" - "gopkg.in/h2non/filetype.v1" ) var FileTooLargeError = errors.New("File too large.") @@ -263,11 +263,11 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) { header = header[:n] // Determine the type of file from header - kind, err := filetype.Match(header) - if err != nil || kind.Extension == "unknown" { + kind := mimetype.Detect(header) + if len(kind.Extension()) < 2 { extension = "file" } else { - extension = kind.Extension + extension = kind.Extension()[1:] // remove leading "." } } From 8ed205181a5e83b86ff3b782434f2ca6062ab424 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Fri, 14 Aug 2020 00:32:55 -0700 Subject: [PATCH 111/233] Add buildx GH action for multi-arch docker images --- .github/workflows/buildx.yaml | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/buildx.yaml diff --git a/.github/workflows/buildx.yaml b/.github/workflows/buildx.yaml new file mode 100644 index 0000000..c8b6cfe --- /dev/null +++ b/.github/workflows/buildx.yaml @@ -0,0 +1,61 @@ +name: buildx + +on: + push: + branches: master + tags: + - v* + +jobs: + buildx: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Prepare + id: prepare + run: | + DOCKER_IMAGE=crazymax/diun + DOCKER_PLATFORMS=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386 + VERSION=version-${GITHUB_REF#refs/tags/v} + TAGS="--tag ${DOCKER_IMAGE}:${VERSION} --tag ${DOCKER_IMAGE}:latest" + + echo ::set-output name=docker_image::${DOCKER_IMAGE} + echo ::set-output name=version::${VERSION} + echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \ + --build-arg VERSION=${VERSION} \ + --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ + --build-arg VCS_REF=${GITHUB_SHA::8} \ + ${TAGS} --file ./test/Dockerfile ./test + - + name: Set up Docker Buildx + uses: crazy-max/ghaction-docker-buildx@v3 + - + name: Docker Buildx (build) + run: | + docker buildx build --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }} + - + name: Docker Login + if: success() + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin + - + name: Docker Buildx (push) + if: success() + run: | + docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} + - + name: Docker Check Manifest + if: always() + run: | + docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }} + - + name: Clear + if: always() + run: | + rm -f ${HOME}/.docker/config.json From 965d5f6c29e584cc84febf70762d95ee27f14d88 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Fri, 14 Aug 2020 00:39:53 -0700 Subject: [PATCH 112/233] Fix GH action --- .github/workflows/buildx.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/buildx.yaml b/.github/workflows/buildx.yaml index c8b6cfe..50e8601 100644 --- a/.github/workflows/buildx.yaml +++ b/.github/workflows/buildx.yaml @@ -2,9 +2,8 @@ name: buildx on: push: - branches: master tags: - - v* + - 'v*' jobs: buildx: @@ -17,7 +16,7 @@ jobs: name: Prepare id: prepare run: | - DOCKER_IMAGE=crazymax/diun + DOCKER_IMAGE=andreimarcu/linx-server DOCKER_PLATFORMS=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386 VERSION=version-${GITHUB_REF#refs/tags/v} TAGS="--tag ${DOCKER_IMAGE}:${VERSION} --tag ${DOCKER_IMAGE}:latest" From a2e00d06e03a1de04e85b872a1cbe27c204acf88 Mon Sep 17 00:00:00 2001 From: mutantmonkey <67266+mutantmonkey@users.noreply.github.com> Date: Fri, 14 Aug 2020 07:40:52 +0000 Subject: [PATCH 113/233] Clarify how metadata is stored with the S3 backend (#223) This was suggested in #221. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d509689..ed90db5 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ The following storage backends are available: |Name|Notes|Options |----|-----|------- |LocalFS|Enabled by default, this backend uses the filesystem|```filespath = files/``` -- Path to store uploads (default is files/)
```metapath = meta/``` -- Path to store information about uploads (default is meta/)| -|S3|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket).

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).|```s3-endpoint = https://...``` -- S3 endpoint
```s3-region = us-east-1``` -- S3 region
```s3-bucket = mybucket``` -- S3 bucket to use for files and metadata
```s3-force-path-style = true``` (optional) -- force path-style addresing (e.g. https://s3.amazonaws.com/linx/example.txt)

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token| +|S3|Use with any S3-compatible provider.
This implementation will stream files through the linx instance (every download will request and stream the file from the S3 bucket). File metadata will be stored as tags on the object in the bucket.

For high-traffic environments, one might consider using an external caching layer such as described [in this article](https://blog.sentry.io/2017/03/01/dodging-s3-downtime-with-nginx-and-haproxy.html).|```s3-endpoint = https://...``` -- S3 endpoint
```s3-region = us-east-1``` -- S3 region
```s3-bucket = mybucket``` -- S3 bucket to use for files and metadata
```s3-force-path-style = true``` (optional) -- force path-style addresing (e.g. https://s3.amazonaws.com/linx/example.txt)

Environment variables to provide:
```AWS_ACCESS_KEY_ID``` -- the S3 access key
```AWS_SECRET_ACCESS_KEY ``` -- the S3 secret key
```AWS_SESSION_TOKEN``` (optional) -- the S3 session token| #### SSL with built-in server From 456274c1b9d90f8e688d9fb4f4207963dbecf3e4 Mon Sep 17 00:00:00 2001 From: mutantmonkey <67266+mutantmonkey@users.noreply.github.com> Date: Fri, 14 Aug 2020 07:42:45 +0000 Subject: [PATCH 114/233] Split and move auth into a separate package (#224) * Split and move auth into a separate package This change will make it easier to implement additional authentication methods, such as OpenID Connect. For now, only the existing "apikeys" authentication method is supported. * Use absolute site prefix to prevent redirect loop --- auth.go => auth/apikeys/apikeys.go | 77 +++++++++++++------- auth_test.go => auth/apikeys/apikeys_test.go | 8 +- server.go | 27 ++----- upload.go | 12 ++- 4 files changed, 68 insertions(+), 56 deletions(-) rename auth.go => auth/apikeys/apikeys.go (53%) rename auth_test.go => auth/apikeys/apikeys_test.go (64%) diff --git a/auth.go b/auth/apikeys/apikeys.go similarity index 53% rename from auth.go rename to auth/apikeys/apikeys.go index 3dc5ba6..d2a592d 100644 --- a/auth.go +++ b/auth/apikeys/apikeys.go @@ -1,4 +1,4 @@ -package main +package apikeys import ( "bufio" @@ -24,16 +24,18 @@ const ( type AuthOptions struct { AuthFile string UnauthMethods []string + BasicAuth bool + SiteName string + SitePath string } -type auth struct { +type ApiKeysMiddleware struct { successHandler http.Handler - failureHandler http.Handler authKeys []string o AuthOptions } -func readAuthKeys(authFile string) []string { +func ReadAuthKeys(authFile string) []string { var authKeys []string f, err := os.Open(authFile) @@ -55,7 +57,7 @@ func readAuthKeys(authFile string) []string { return authKeys } -func checkAuth(authKeys []string, key string) (result bool, err error) { +func CheckAuth(authKeys []string, key string) (result bool, err error) { checkKey, err := scrypt.Key([]byte(key), []byte(scryptSalt), scryptN, scryptr, scryptp, scryptKeyLen) if err != nil { return @@ -73,53 +75,74 @@ func checkAuth(authKeys []string, key string) (result bool, err error) { return } -func (a auth) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if sliceContains(a.o.UnauthMethods, r.Method) { +func (a ApiKeysMiddleware) getSitePrefix() string { + prefix := a.o.SitePath + if len(prefix) <= 0 || prefix[0] != '/' { + prefix = "/" + prefix + } + return prefix +} + +func (a ApiKeysMiddleware) goodAuthorizationHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", a.getSitePrefix()) + w.WriteHeader(http.StatusFound) +} + +func (a ApiKeysMiddleware) badAuthorizationHandler(w http.ResponseWriter, r *http.Request) { + if a.o.BasicAuth { + rs := "" + if a.o.SiteName != "" { + rs = fmt.Sprintf(` realm="%s"`, a.o.SiteName) + } + w.Header().Set("WWW-Authenticate", `Basic`+rs) + } + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) +} + +func (a ApiKeysMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var successHandler http.Handler + prefix := a.getSitePrefix() + + if r.URL.Path == prefix+"auth" { + successHandler = http.HandlerFunc(a.goodAuthorizationHandler) + } else { + successHandler = a.successHandler + } + + if sliceContains(a.o.UnauthMethods, r.Method) && r.URL.Path != prefix+"auth" { // allow unauthenticated methods - a.successHandler.ServeHTTP(w, r) + successHandler.ServeHTTP(w, r) return } key := r.Header.Get("Linx-Api-Key") - if key == "" && Config.basicAuth { + if key == "" && a.o.BasicAuth { _, password, ok := r.BasicAuth() if ok { key = password } } - result, err := checkAuth(a.authKeys, key) + result, err := CheckAuth(a.authKeys, key) if err != nil || !result { - a.failureHandler.ServeHTTP(w, r) + http.HandlerFunc(a.badAuthorizationHandler).ServeHTTP(w, r) return } - a.successHandler.ServeHTTP(w, r) + successHandler.ServeHTTP(w, r) } -func UploadAuth(o AuthOptions) func(*web.C, http.Handler) http.Handler { +func NewApiKeysMiddleware(o AuthOptions) func(*web.C, http.Handler) http.Handler { fn := func(c *web.C, h http.Handler) http.Handler { - return auth{ + return ApiKeysMiddleware{ successHandler: h, - failureHandler: http.HandlerFunc(badAuthorizationHandler), - authKeys: readAuthKeys(o.AuthFile), + authKeys: ReadAuthKeys(o.AuthFile), o: o, } } return fn } -func badAuthorizationHandler(w http.ResponseWriter, r *http.Request) { - if Config.basicAuth { - rs := "" - if Config.siteName != "" { - rs = fmt.Sprintf(` realm="%s"`, Config.siteName) - } - w.Header().Set("WWW-Authenticate", `Basic`+rs) - } - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) -} - func sliceContains(slice []string, s string) bool { for _, v := range slice { if s == v { diff --git a/auth_test.go b/auth/apikeys/apikeys_test.go similarity index 64% rename from auth_test.go rename to auth/apikeys/apikeys_test.go index ded98b0..3c2b8e6 100644 --- a/auth_test.go +++ b/auth/apikeys/apikeys_test.go @@ -1,4 +1,4 @@ -package main +package apikeys import ( "testing" @@ -10,15 +10,15 @@ func TestCheckAuth(t *testing.T) { "vFpNprT9wbHgwAubpvRxYCCpA2FQMAK6hFqPvAGrdZo=", } - if r, err := checkAuth(authKeys, ""); err != nil && r { + if r, err := CheckAuth(authKeys, ""); err != nil && r { t.Fatal("Authorization passed for empty key") } - if r, err := checkAuth(authKeys, "thisisnotvalid"); err != nil && r { + if r, err := CheckAuth(authKeys, "thisisnotvalid"); err != nil && r { t.Fatal("Authorization passed for invalid key") } - if r, err := checkAuth(authKeys, "haPVipRnGJ0QovA9nyqK"); err != nil && !r { + if r, err := CheckAuth(authKeys, "haPVipRnGJ0QovA9nyqK"); err != nil && !r { t.Fatal("Authorization failed for valid key") } } diff --git a/server.go b/server.go index dae3491..4d06db9 100644 --- a/server.go +++ b/server.go @@ -16,6 +16,7 @@ import ( "time" rice "github.com/GeertJohan/go.rice" + "github.com/andreimarcu/linx-server/auth/apikeys" "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/backends/localfs" "github.com/andreimarcu/linx-server/backends/s3" @@ -110,9 +111,12 @@ func setup() *web.Mux { mux.Use(AddHeaders(Config.addHeaders)) if Config.authFile != "" { - mux.Use(UploadAuth(AuthOptions{ + mux.Use(apikeys.NewApiKeysMiddleware(apikeys.AuthOptions{ AuthFile: Config.authFile, UnauthMethods: []string{"GET", "HEAD", "OPTIONS", "TRACE"}, + BasicAuth: Config.basicAuth, + SiteName: Config.siteName, + SitePath: Config.sitePath, })) } @@ -196,29 +200,10 @@ func setup() *web.Mux { mux.Get(Config.sitePath+"upload/", uploadRemote) if Config.remoteAuthFile != "" { - remoteAuthKeys = readAuthKeys(Config.remoteAuthFile) + remoteAuthKeys = apikeys.ReadAuthKeys(Config.remoteAuthFile) } } - if Config.basicAuth { - options := AuthOptions{ - AuthFile: Config.authFile, - UnauthMethods: []string{}, - } - okFunc := func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Location", Config.sitePath) - w.WriteHeader(http.StatusFound) - } - authHandler := auth{ - successHandler: http.HandlerFunc(okFunc), - failureHandler: http.HandlerFunc(badAuthorizationHandler), - authKeys: readAuthKeys(Config.authFile), - o: options, - } - mux.Head(Config.sitePath+"auth", authHandler) - mux.Get(Config.sitePath+"auth", authHandler) - } - mux.Post(Config.sitePath+"upload", uploadPostHandler) mux.Post(Config.sitePath+"upload/", uploadPostHandler) mux.Put(Config.sitePath+"upload", uploadPutHandler) diff --git a/upload.go b/upload.go index 3cac122..bda0d7f 100644 --- a/upload.go +++ b/upload.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/andreimarcu/linx-server/auth/apikeys" "github.com/andreimarcu/linx-server/backends" "github.com/andreimarcu/linx-server/expiry" "github.com/dchest/uniuri" @@ -166,13 +167,16 @@ func uploadRemote(c web.C, w http.ResponseWriter, r *http.Request) { key = password } } - result, err := checkAuth(remoteAuthKeys, key) + result, err := apikeys.CheckAuth(remoteAuthKeys, key) if err != nil || !result { if Config.basicAuth { - badAuthorizationHandler(w, r) - } else { - unauthorizedHandler(c, w, r) + rs := "" + if Config.siteName != "" { + rs = fmt.Sprintf(` realm="%s"`, Config.siteName) + } + w.Header().Set("WWW-Authenticate", `Basic`+rs) } + unauthorizedHandler(c, w, r) return } } From 9a5fc11dffe5d2ac6cb6e7edfa97bccd417285ed Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Fri, 14 Aug 2020 00:52:25 -0700 Subject: [PATCH 115/233] Fix GH action (again) --- .github/workflows/buildx.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buildx.yaml b/.github/workflows/buildx.yaml index 50e8601..1938731 100644 --- a/.github/workflows/buildx.yaml +++ b/.github/workflows/buildx.yaml @@ -27,7 +27,7 @@ jobs: --build-arg VERSION=${VERSION} \ --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ --build-arg VCS_REF=${GITHUB_SHA::8} \ - ${TAGS} --file ./test/Dockerfile ./test + ${TAGS} --file Dockerfile . - name: Set up Docker Buildx uses: crazy-max/ghaction-docker-buildx@v3 From ef99024433df488e2aa2d063044ee05f4d01ac78 Mon Sep 17 00:00:00 2001 From: tuxx Date: Sat, 17 Oct 2020 01:55:11 +0200 Subject: [PATCH 116/233] Add LinxShare android client to README.md (#246) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed90db5..62f4d98 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ You can see what it looks like using the demo: [https://demo.linx-server.net/](h - Display common filetypes (image, video, audio, markdown, pdf) - Display syntax-highlighted code with in-place editing -- Documented API with keys if need to restrict uploads (can use [linx-client](https://github.com/andreimarcu/linx-client) for uploading through command-line) +- Documented API with keys if need to restrict uploads (can use [linx-client](https://github.com/andreimarcu/linx-client) for uploading through command-line, or [LinxShare](https://github.com/iksteen/LinxShare/) client for android - also available on the (Play Store)[https://play.google.com/store/apps/details?id=org.thegraveyard.linxshare] ) - Torrent download of files using web seeding - File expiry, deletion key, file access key, and random filename options From 91b9885ac6d6c9bf7e2837e8daf7ff3b1facea4c Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Fri, 16 Oct 2020 16:57:09 -0700 Subject: [PATCH 117/233] Update README.md --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 62f4d98..622d950 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,20 @@ Self-hosted file/media sharing website. You can see what it looks like using the demo: [https://demo.linx-server.net/](https://demo.linx-server.net/) +### Clients +**Official** +- CLI: **linx-client** - [Source](https://github.com/andreimarcu/linx-client) + +**Unofficial** +- Android: **LinxShare** - [Source](https://github.com/iksteen/LinxShare/) | [Google Play](https://play.google.com/store/apps/details?id=org.thegraveyard.linxshare) +- CLI: **golinx** - [Source](https://github.com/mutantmonkey/golinx) + + ### Features - Display common filetypes (image, video, audio, markdown, pdf) - Display syntax-highlighted code with in-place editing -- Documented API with keys if need to restrict uploads (can use [linx-client](https://github.com/andreimarcu/linx-client) for uploading through command-line, or [LinxShare](https://github.com/iksteen/LinxShare/) client for android - also available on the (Play Store)[https://play.google.com/store/apps/details?id=org.thegraveyard.linxshare] ) +- Documented API with keys for restricting uploads - Torrent download of files using web seeding - File expiry, deletion key, file access key, and random filename options @@ -60,8 +69,8 @@ Ideally, you would use a reverse proxy such as nginx or caddy to handle TLS cert #### Using a binary release -1. Grab the latest binary from the [releases](https://github.com/andreimarcu/linx-server/releases) -2. Run ```./linx-server``` +1. Grab the latest binary from the [releases](https://github.com/andreimarcu/linx-server/releases), then run ```go install``` +2. Run ```linx-server -config path/to/linx-server.conf``` Usage From 486cc6ff778171eb2b5b231db2cc52d5f484352f Mon Sep 17 00:00:00 2001 From: Steven Tang Date: Mon, 30 Nov 2020 06:09:53 +1100 Subject: [PATCH 118/233] Remove entrypoint from sample docker-compose.yml (#252) Resolves #225 Entrypoint in Dockerfile specifies default bind, filespath, metapath. Having entrypoint in docker-compose.yml will remove those defaults. Without the entrypoint line, the command executed is: `/usr/local/bin/linx-server -bind=0.0.0.0:8080 -filespath=/data/files/ -metapath=/data/meta/ -config /data/linx-server.conf` --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 622d950..cf285b9 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ services: linx-server: container_name: linx-server image: andreimarcu/linx-server - entrypoint: /usr/local/bin/linx-server command: -config /data/linx-server.conf volumes: - /path/to/files:/data/files From 94f63c204506d595194bd52b5f3d3c1ba19923f0 Mon Sep 17 00:00:00 2001 From: Andrei Marcu Date: Mon, 25 Jan 2021 11:29:55 -0800 Subject: [PATCH 119/233] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cf285b9..16318be 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ +## Seeking new maintainer! +If anyone would be interested in taking on this project, I would be willing to transfer it over as development and work on issues has mostly stalled at this point. +Please [respond to this issue](https://github.com/andreimarcu/linx-server/issues/266) if interested! + +--- + linx-server ====== [![Build Status](https://travis-ci.org/andreimarcu/linx-server.svg?branch=master)](https://travis-ci.org/andreimarcu/linx-server) From 63975c2019d9a943be9f6d6f122239749b0e75f4 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Tue, 9 Feb 2021 20:00:20 -0500 Subject: [PATCH 120/233] Extra footer text block test. --- server.go | 2 ++ templates.go | 1 + templates/base.html | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 4d06db9..903288f 100644 --- a/server.go +++ b/server.go @@ -304,6 +304,8 @@ func main() { "path to directory containing .md files to render as custom pages") flag.Uint64Var(&Config.cleanupEveryMinutes, "cleanup-every-minutes", 0, "How often to clean up expired files in minutes (default is 0, which means files will be cleaned up as they are accessed)") + flag.StringVar(&Config.extraFooterText, "extrafootertext", "", + "Extra text above the footer for notices.") iniflags.Parse() diff --git a/templates.go b/templates.go index 7d38b51..6dcf852 100644 --- a/templates.go +++ b/templates.go @@ -87,6 +87,7 @@ func renderTemplate(tpl *pongo2.Template, context pongo2.Context, r *http.Reques context["sitepath"] = Config.sitePath context["selifpath"] = Config.selifPath context["custom_pages_names"] = customPagesNames + context["extra_footer_text"] = Config.extraFooterText var a string if Config.authFile == "" { diff --git a/templates/base.html b/templates/base.html index 7e4f82d..847a995 100644 --- a/templates/base.html +++ b/templates/base.html @@ -29,7 +29,9 @@
{% block content %}{% endblock %} - +
+ {% if disable_access_key != true %} - + {% endif %} Randomize filename + {% if default_randomized && !(forcerandom) %} checked {% endif %} /> Randomize filename
From 4579be1048c5faf0761479c1f92c7f5b4580d664 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Sat, 27 Feb 2021 21:42:19 -0500 Subject: [PATCH 215/233] Update index.html --- templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.html b/templates/index.html index 01ec49e..ab699e2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -21,7 +21,7 @@ + {% if default_randomized or not forcerandom %} checked {% endif %} /> Randomize filename
From 59cf8dbfee43831ed7891844b00bc5c0a4c3bdc2 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Sat, 27 Feb 2021 21:53:29 -0500 Subject: [PATCH 216/233] Update index.html --- templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/index.html b/templates/index.html index ab699e2..8060af2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -21,7 +21,7 @@ + {% if (default_randomize && !( forcerandom)) || forcerandom %} checked {% endif %} /> Randomize filename
From fae511f7d55a06c151d2a50b2739b0cf7501fcd0 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Wed, 3 Mar 2021 00:06:44 -0500 Subject: [PATCH 217/233] Update Readme to include new feature descriptions. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 16318be..2c8fb83 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,11 @@ maxexpiry = 86400 | ```nologs = true``` | (optionally) disable request logs in stdout | ```force-random-filename = true``` | (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 +| ```extra-footer-text = "..."``` | (optionally) Extra text above the footer for notices. +| ```max-duration-time = 0``` | Time till expiry for files over max-duration-size. (Default is 0 for no-expiry.) +| ```max-duration-size = 4294967296``` | Size of file before max-duration-time is used to determine expiry max time. (Default is 4GB) +| ```disable-access-key = true``` | Disables access key usage. (Default is false.) +| ```default-random-filename = true``` | Makes it so the random filename is not default if set false. (Default is true.) #### Cleaning up expired files From d43dcf4acdbdba303bf417da750067cfdaae501f Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Sun, 7 Mar 2021 23:04:52 -0500 Subject: [PATCH 218/233] Update default text for new features. --- server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 985c769..82a72e3 100644 --- a/server.go +++ b/server.go @@ -313,10 +313,10 @@ func main() { "How often to clean up expired files in minutes (default is 0, which means files will be cleaned up as they are accessed)") flag.StringVar(&Config.extraFooterText, "extra-footer-text", "", "Extra text above the footer for notices.") - flag.Uint64Var(&Config.maxDurationTime, "max-duration-time", 0, "Time till expiry for files over max-duration-size") - flag.Int64Var(&Config.maxDurationSize, "max-duration-size", 4*1024*1024*1024, "Size of file before max-duration-time is used to determine expiry max time.") - flag.BoolVar(&Config.disableAccessKey, "disable-access-key", false, "Disables access key usage") - flag.BoolVar(&Config.defaultRandomFilename, "default-random-filename", true, "Makes it so the random filename is not default if set false. Default true.") + flag.Uint64Var(&Config.maxDurationTime, "max-duration-time", 0, "Time till expiry for files over max-duration-size. (Default is 0 for no-expiry.)") + flag.Int64Var(&Config.maxDurationSize, "max-duration-size", 4*1024*1024*1024, "Size of file before max-duration-time is used to determine expiry max time. (Default is 4GB)") + flag.BoolVar(&Config.disableAccessKey, "disable-access-key", false, "Disables access key usage. (Default is false.)") + flag.BoolVar(&Config.defaultRandomFilename, "default-random-filename", true, "Makes it so the random filename is not default if set false. (Default is true.)") iniflags.Parse() mux := setup() From ac7c088e91e444ddc05e9e83138d7320e1a47314 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Wed, 17 Mar 2021 19:24:07 -0400 Subject: [PATCH 219/233] Merge remote-tracking branch 'upstream/master' --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 2c8fb83..a603003 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,3 @@ - -## Seeking new maintainer! -If anyone would be interested in taking on this project, I would be willing to transfer it over as development and work on issues has mostly stalled at this point. -Please [respond to this issue](https://github.com/andreimarcu/linx-server/issues/266) if interested! - ---- - linx-server ====== [![Build Status](https://travis-ci.org/andreimarcu/linx-server.svg?branch=master)](https://travis-ci.org/andreimarcu/linx-server) From 5b774469ea1be43c14602d2f9dd80e4783b88955 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Wed, 17 Mar 2021 19:34:36 -0400 Subject: [PATCH 220/233] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a603003..12aeaac 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ linx-server ====== -[![Build Status](https://travis-ci.org/andreimarcu/linx-server.svg?branch=master)](https://travis-ci.org/andreimarcu/linx-server) Self-hosted file/media sharing website. ### Demo -You can see what it looks like using the demo: [https://demo.linx-server.net/](https://demo.linx-server.net/) +You can see what it looks like using the demo: [https://put.icu/](https://put.icu/) ### Clients From 7d0950e54c3015c4dce220e06de7c59b5035c901 Mon Sep 17 00:00:00 2001 From: William Oldham Date: Sun, 11 Jul 2021 00:09:57 +0100 Subject: [PATCH 221/233] Update image.html --- templates/display/image.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/display/image.html b/templates/display/image.html index 985c504..8dcd844 100644 --- a/templates/display/image.html +++ b/templates/display/image.html @@ -1,6 +1,7 @@ {% extends "base.html" %} {% block head %} + {% endblock %} @@ -8,4 +9,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} From 084ac233ffaa51c37b32ced8126bd4fa8c176dc1 Mon Sep 17 00:00:00 2001 From: ZizzyDizzyMC Date: Fri, 20 Aug 2021 12:13:58 -0400 Subject: [PATCH 222/233] Repo Link update. Added link to this fork of Linx-server. --- templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index 17b1ea2..54fd368 100644 --- a/templates/base.html +++ b/templates/base.html @@ -30,7 +30,7 @@ {% block content %}{% endblock %}
From d63a875b12e4bf370c31ec4ee4f1d79b74c8f0e8 Mon Sep 17 00:00:00 2001 From: luckman212 Date: Sat, 9 Oct 2021 11:36:53 -0400 Subject: [PATCH 223/233] add default expiry config parameter --- pages.go | 12 +++++++----- server.go | 3 +++ templates/index.html | 2 +- templates/paste.html | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pages.go b/pages.go index ae8de42..6492651 100644 --- a/pages.go +++ b/pages.go @@ -21,9 +21,10 @@ const ( func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { err := renderTemplate(Templates["index.html"], pongo2.Context{ - "maxsize": Config.maxSize, - "expirylist": listExpirationTimes(), - "forcerandom": Config.forceRandomFilename, + "maxsize": Config.maxSize, + "expirylist": listExpirationTimes(), + "expirydefault": Config.defaultExpiry, + "forcerandom": Config.forceRandomFilename, }, r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -32,8 +33,9 @@ func indexHandler(c web.C, w http.ResponseWriter, r *http.Request) { func pasteHandler(c web.C, w http.ResponseWriter, r *http.Request) { err := renderTemplate(Templates["paste.html"], pongo2.Context{ - "expirylist": listExpirationTimes(), - "forcerandom": Config.forceRandomFilename, + "expirylist": listExpirationTimes(), + "expirydefault": Config.defaultExpiry, + "forcerandom": Config.forceRandomFilename, }, r, w) if err != nil { oopsHandler(c, w, r, RespHTML, "") diff --git a/server.go b/server.go index 82a72e3..41cd932 100644 --- a/server.go +++ b/server.go @@ -56,6 +56,7 @@ var Config struct { xFrameOptions string maxSize int64 maxExpiry uint64 + defaultExpiry uint64 realIp bool noLogs bool allowHotlink bool @@ -264,6 +265,8 @@ func main() { "maximum upload file size in bytes (default 4GB)") flag.Uint64Var(&Config.maxExpiry, "maxexpiry", 0, "maximum expiration time in seconds (default is 0, which is no expiry)") + flag.Uint64Var(&Config.defaultExpiry, "default-expiry", 86400, + "default expiration time in seconds (default is 86400, which is 1 day)") flag.StringVar(&Config.certFile, "certfile", "", "path to ssl certificate (for https)") flag.StringVar(&Config.keyFile, "keyfile", "", diff --git a/templates/index.html b/templates/index.html index 8060af2..3958788 100644 --- a/templates/index.html +++ b/templates/index.html @@ -28,7 +28,7 @@