summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--roseh.moe.go115
-rw-r--r--static/htmx-ext-sse.js8
-rw-r--r--templates/wormhole.html.template32
3 files changed, 64 insertions, 91 deletions
diff --git a/roseh.moe.go b/roseh.moe.go
index 696b64e..3a53208 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -1,6 +1,7 @@
package main
import (
+ "context"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
@@ -13,6 +14,7 @@ import (
"html/template"
"io"
"log"
+ "mime/multipart"
"net/http"
"os"
"strings"
@@ -198,24 +200,59 @@ func makeHole() string {
return strings.Join(words, "-")
}
+func newWormhole(w http.ResponseWriter, r *http.Request) {
+ http.Redirect(w, r, "/wormhole/"+makeHole()+"/upload", http.StatusSeeOther)
+}
+
func wormhole(w http.ResponseWriter, r *http.Request) {
- if err := wormholeTemplate.Execute(w, wormholeTemplateArgs{Self: *selfURL, Hole: makeHole()}); err != nil {
+ if err := wormholeTemplate.Execute(w, wormholeTemplateArgs{Self: *selfURL, Hole: r.PathValue("hole")}); err != nil {
log.Printf("Warning: wormhole: %s", err)
}
}
type wormholeConn struct {
done chan struct{}
+ r *multipart.Part
w http.ResponseWriter
}
var wormholeConnsMu sync.Mutex
-var wormholeConns = make(map[string]*wormholeConn)
+var wormholeConns = make(map[string]wormholeConn)
-var wormholeNotifyMu sync.Mutex
-var wormholeNotify = make(map[string]chan struct{})
+func (c wormholeConn) wormholeCopy(ctx context.Context, hole string) error {
+ wormholeConnsMu.Lock()
+ prevConn, ok := wormholeConns[hole]
+ if !ok {
+ wormholeConns[hole] = c
+ wormholeConnsMu.Unlock()
+ defer func() {
+ wormholeConnsMu.Lock()
+ delete(wormholeConns, hole)
+ wormholeConnsMu.Unlock()
+ }()
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-c.done:
+ return nil
+ }
+ }
+ wormholeConnsMu.Unlock()
+ defer close(prevConn.done)
+ if c.w == nil {
+ c.w = prevConn.w
+ } else {
+ c.r = prevConn.r
+ }
+ c.w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+c.r.FileName())
+ if _, err := io.Copy(c.w, c.r); err != nil {
+ return fmt.Errorf("copy: %s", err)
+ }
+ return nil
+}
func wormholeSend(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
reader, err := r.MultipartReader()
if err != nil {
http.Error(w, fmt.Sprintf("Not a multipart/form-data request: %s", err), http.StatusBadRequest)
@@ -230,19 +267,8 @@ func wormholeSend(w http.ResponseWriter, r *http.Request) {
continue
}
hole := r.PathValue("hole")
- wormholeConnsMu.Lock()
- conn := wormholeConns[hole]
- delete(wormholeConns, hole)
- wormholeConnsMu.Unlock()
- if conn == nil {
- http.Error(w, "no such connection", http.StatusBadRequest)
- return
- }
- defer close(conn.done)
- conn.w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+part.FileName())
- if _, err := io.Copy(conn.w, part); err != nil {
+ if err := (wormholeConn{done: make(chan struct{}), r: part}).wormholeCopy(ctx, hole); err != nil {
http.Error(w, fmt.Sprintf("Error during copy: %s", err), http.StatusServiceUnavailable)
- return
}
fmt.Fprintf(w, "uploaded!")
return
@@ -253,56 +279,9 @@ func wormholeSend(w http.ResponseWriter, r *http.Request) {
func wormholeRecv(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hole := r.PathValue("hole")
- conn := &wormholeConn{
- done: make(chan struct{}),
- w: w,
- }
- wormholeConnsMu.Lock()
- wormholeConns[hole] = conn
- wormholeConnsMu.Unlock()
- defer func() {
- wormholeConnsMu.Lock()
- delete(wormholeConns, hole)
- wormholeConnsMu.Unlock()
- }()
- wormholeNotifyMu.Lock()
- notify := wormholeNotify[hole]
- wormholeNotifyMu.Unlock()
- if notify == nil {
- http.Error(w, "no such connection", http.StatusBadRequest)
- return
- }
- select {
- case notify <- struct{}{}:
- default:
- http.Error(w, "connection not ready", http.StatusBadRequest)
- return
- }
- select {
- case <-ctx.Done():
- case <-conn.done:
- }
-}
-
-func wormholeReady(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
- hole := r.PathValue("hole")
- notify := make(chan struct{})
- wormholeNotifyMu.Lock()
- wormholeNotify[hole] = notify
- wormholeNotifyMu.Unlock()
- defer func() {
- wormholeNotifyMu.Lock()
- delete(wormholeNotify, hole)
- wormholeNotifyMu.Unlock()
- }()
- w.Header().Set("Content-Type", "text/event-stream")
- select {
- case <-ctx.Done():
- return
- case <-notify:
+ if err := (wormholeConn{done: make(chan struct{}), w: w}).wormholeCopy(ctx, hole); err != nil {
+ http.Error(w, fmt.Sprintf("Error during copy: %s", err), http.StatusServiceUnavailable)
}
- fmt.Fprintf(w, "event: ready\ndata:\n\n")
}
func mac(msg []byte) []byte {
@@ -551,10 +530,10 @@ func main() {
}
http.HandleFunc("GET /pong", pong)
- http.HandleFunc("GET /wormhole", wormhole)
- http.HandleFunc("POST /wormhole/{hole}", wormholeSend)
+ http.HandleFunc("GET /wormhole", newWormhole)
+ http.HandleFunc("GET /wormhole/{hole}/upload", wormhole)
+ http.HandleFunc("POST /wormhole/{hole}/upload", wormholeSend)
http.HandleFunc("GET /wormhole/{hole}", wormholeRecv)
- http.HandleFunc("GET /wormhole/{hole}/ready", wormholeReady)
http.HandleFunc("POST /login", login)
http.HandleFunc("GET /notepad", notepad)
http.HandleFunc("POST /notepad", autosave)
diff --git a/static/htmx-ext-sse.js b/static/htmx-ext-sse.js
deleted file mode 100644
index 6d087ec..0000000
--- a/static/htmx-ext-sse.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * Minified by jsDelivr using Terser v5.37.0.
- * Original file: /npm/htmx-ext-sse@2.2.2/sse.js
- *
- * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
- */
-!function(){var e;function t(e){return new EventSource(e,{withCredentials:!0})}function n(t){if(e.getAttributeValue(t,"sse-swap")){if(null==(u=e.getClosestMatch(t,i)))return null;for(var n=e.getInternalData(u).sseEventSource,r=e.getAttributeValue(t,"sse-swap").split(","),o=0;o<r.length;o++){const i=r[o].trim(),c=function(r){s(u)||(e.bodyContains(t)?e.triggerEvent(t,"htmx:sseBeforeMessage",r)&&(a(t,r.data),e.triggerEvent(t,"htmx:sseMessage",r)):n.removeEventListener(i,c))};e.getInternalData(t).sseEventListener=c,n.addEventListener(i,c)}}if(e.getAttributeValue(t,"hx-trigger")){var u;if(null==(u=e.getClosestMatch(t,i)))return null;n=e.getInternalData(u).sseEventSource;e.getTriggerSpecs(t).forEach((function(r){if("sse:"===r.trigger.slice(0,4)){var a=function(i){s(u)||(e.bodyContains(t)||n.removeEventListener(r.trigger.slice(4),a),htmx.trigger(t,r.trigger,i),htmx.trigger(t,"htmx:sseMessage",i))};e.getInternalData(t).sseEventListener=a,n.addEventListener(r.trigger.slice(4),a)}}))}}function r(t,a){if(null==t)return null;if(e.getAttributeValue(t,"sse-connect")){var i=e.getAttributeValue(t,"sse-connect");if(null==i)return;!function(t,a,i){var o=htmx.createEventSource(a);o.onerror=function(n){if(e.triggerErrorEvent(t,"htmx:sseError",{error:n,source:o}),!s(t)&&o.readyState===EventSource.CLOSED){i=i||0;var a=500*(i=Math.max(Math.min(2*i,128),1));window.setTimeout((function(){r(t,i)}),a)}},o.onopen=function(r){if(e.triggerEvent(t,"htmx:sseOpen",{source:o}),i&&i>0){const e=t.querySelectorAll("[sse-swap], [data-sse-swap], [hx-trigger], [data-hx-trigger]");for(let t=0;t<e.length;t++)n(e[t]);i=0}},e.getInternalData(t).sseEventSource=o;var u=e.getAttributeValue(t,"sse-close");u&&o.addEventListener(u,(function(){e.triggerEvent(t,"htmx:sseClose",{source:o,type:"message"}),o.close()}))}(t,i,a)}n(t)}function s(t){if(!e.bodyContains(t)){var n=e.getInternalData(t).sseEventSource;if(null!=n)return e.triggerEvent(t,"htmx:sseClose",{source:n,type:"nodeMissing"}),n.close(),!0}return!1}function a(t,n){e.withExtensions(t,(function(e){n=e.transformResponse(n,null,t)}));var r=e.getSwapSpecification(t),s=e.getTarget(t);e.swap(s,n,r)}function i(t){return null!=e.getInternalData(t).sseEventSource}htmx.defineExtension("sse",{init:function(n){e=n,null==htmx.createEventSource&&(htmx.createEventSource=t)},getSelectors:function(){return["[sse-connect]","[data-sse-connect]","[sse-swap]","[data-sse-swap]"]},onEvent:function(t,n){var s=n.target||n.detail.elt;switch(t){case"htmx:beforeCleanupElement":var a=e.getInternalData(s),i=a.sseEventSource;return void(i&&(e.triggerEvent(s,"htmx:sseClose",{source:i,type:"nodeReplaced"}),a.sseEventSource.close()));case"htmx:afterProcessNode":r(s)}}})}();
-//# sourceMappingURL=/sm/97a20e642efcf59a346095b79d9e73120e258a50836a64ba297a1990d62f8cd5.map \ No newline at end of file
diff --git a/templates/wormhole.html.template b/templates/wormhole.html.template
index 4d67a98..5703949 100644
--- a/templates/wormhole.html.template
+++ b/templates/wormhole.html.template
@@ -1,19 +1,21 @@
{{define "head"}}
- <script src="/static/htmx.min.js"></script>
- <script src="/static/htmx-ext-sse.js"></script>
<title>Wormhole</title>
{{end}}
-<div
- hx-ext="sse"
- sse-connect="/wormhole/{{.Hole}}/ready">
- <form
- hx-post="/wormhole/{{.Hole}}"
- hx-trigger="sse:ready"
- enctype="multipart/form-data">
- <label for="file-input">1. Choose file</label>
- <input id="file-input" type="file" name="file">
- </form>
-</div>
-
-<p>2. Visit this URL <a href="{{.Self}}/wormhole/{{.Hole}}">{{.Self}}/wormhole/{{.Hole}}</a></p>
+<form
+ method="post"
+ enctype="multipart/form-data">
+ <ol>
+ <li>
+ <label for="file-input">Choose file</label>
+ <input id="file-input" type="file" name="file">
+ </li>
+ <li>
+ <p>Visit this URL <a href="/wormhole/{{.Hole}}">{{.Self}}/wormhole/{{.Hole}}</a></p>
+ </li>
+ <li>
+ <label for="submit">Click here:</label>
+ <input id="submit" type="submit" value="Upload">
+ </li>
+ </ol>
+</form>