forked from claudiodangelis/qrcp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
182 lines (156 loc) · 4.7 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"runtime"
"strings"
"sync"
"github.com/mattn/go-colorable"
"github.com/mdp/qrterminal"
)
var zipFlag = flag.Bool("zip", false, "zip the contents to be transfered")
var forceFlag = flag.Bool("force", false, "ignore saved configuration")
var debugFlag = flag.Bool("debug", false, "increase verbosity")
var quietFlag = flag.Bool("quiet", false, "ignores non critical output")
var portFlag = flag.Int("port", 0, "port to bind the server to")
func main() {
flag.Parse()
config := LoadConfig()
if *forceFlag == true {
config.Delete()
config = LoadConfig()
}
// Check how many arguments are passed
if len(flag.Args()) == 0 {
log.Fatalln("At least one argument is required")
}
// Get Content
content, err := getContent(flag.Args())
if err != nil {
log.Fatalln(err)
}
// Get address
address, err := getAddress(&config)
if err != nil {
log.Fatalln(err)
}
if *portFlag > 0 {
config.Port = *portFlag
}
// Get a TCP Listener bound to a random port, or the user specificed port
// listener, err := net.Listen("tcp", address+port)
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", address, config.Port))
if err != nil {
log.Fatalln(err)
}
address = fmt.Sprintf("%s:%d", address, listener.Addr().(*net.TCPAddr).Port)
randomPath := getRandomURLPath()
generatedAddress := fmt.Sprintf("http://%s/%s", listener.Addr().String(), randomPath)
// Generate the QR code
info("Scan the following QR to start the download.")
info("Make sure that your smartphone is connected to the same WiFi network as this computer.")
info("Your generated address is", generatedAddress)
qrConfig := qrterminal.Config{
HalfBlocks: true,
Level: qrterminal.L,
Writer: os.Stdout,
BlackWhiteChar: "\u001b[37m\u001b[40m\u2584\u001b[0m",
BlackChar: "\u001b[30m\u001b[40m\u2588\u001b[0m",
WhiteBlackChar: "\u001b[30m\u001b[47m\u2585\u001b[0m",
WhiteChar: "\u001b[37m\u001b[47m\u2588\u001b[0m",
}
if runtime.GOOS == "windows" {
qrConfig.HalfBlocks = false
qrConfig.Writer = colorable.NewColorableStdout()
qrConfig.BlackChar = qrterminal.BLACK
qrConfig.WhiteChar = qrterminal.WHITE
}
qrterminal.GenerateWithConfig(generatedAddress, qrConfig)
// Create a server
srv := &http.Server{Addr: address}
// Create channel to send message to stop server
stop := make(chan bool)
// Wait for stop and then shutdown the server,
go func() {
<-stop
if err := srv.Shutdown(context.Background()); err != nil {
log.Println(err)
}
}()
// Gracefully shutdown when an OS signal is received
sig := make(chan os.Signal, 1)
signal.Notify(sig)
go func() {
<-sig
stop <- true
}()
// The handler adds and removes from the sync.WaitGroup
// When the group is zero all requests are completed
// and the server is shutdown
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Wait()
stop <- true
}()
// Create cookie used to verify request is coming from first client to connect
cookie := http.Cookie{Name: "qr-filetransfer", Value: ""}
var initCookie sync.Once
// Define a default handler for the requests
route := fmt.Sprintf("/%s", randomPath)
http.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
// If the cookie's value is empty this is the first connection
// and the initialize the cookie.
// Wrapped in a sync.Once to avoid potential race conditions
if cookie.Value == "" {
if !strings.HasPrefix(r.Header.Get("User-Agent"), "Mozilla") {
http.Error(w, "", http.StatusOK)
return
}
initCookie.Do(func() {
value, err := getSessionID()
if err != nil {
log.Println("Unable to generate session ID", err)
stop <- true
}
cookie.Value = value
http.SetCookie(w, &cookie)
})
} else {
// Check for the expected cookie and value
// If it is missing or doesn't match
// return a 404 status
rcookie, err := r.Cookie(cookie.Name)
if err != nil || rcookie.Value != cookie.Value {
http.Error(w, "", http.StatusNotFound)
return
}
// If the cookie exits and matches
// this is an aadditional request.
// Increment the waitgroup
wg.Add(1)
}
defer wg.Done()
w.Header().Set("Content-Disposition",
"attachment; filename="+content.Name())
http.ServeFile(w, r, content.Path)
})
// Enable TCP keepalives on the listener and start serving requests
if err := (srv.Serve(tcpKeepAliveListener{listener.(*net.TCPListener)})); err != http.ErrServerClosed {
log.Fatalln(err)
}
if content.ShouldBeDeleted {
if err := content.Delete(); err != nil {
log.Println("Unable to delete the content from disk", err)
}
}
if err := config.Update(); err != nil {
log.Println("Unable to update configuration", err)
}
}