main.go (view raw)
1package main
2
3import (
4 "crypto/rand"
5 "database/sql"
6 "flag"
7 "fmt"
8 "github.com/gorilla/sessions"
9 "io"
10 "io/ioutil"
11 "log"
12 mathrand "math/rand"
13 "mime"
14 "os"
15 "path"
16 "path/filepath"
17 "sort"
18 "strings"
19 "sync"
20 "time"
21)
22
23var c Config // global var to hold static configuration
24
25const HIDDEN_FOLDER = ".hidden"
26
27type File struct { // also folders
28 Creator string
29 Name string // includes folder
30 UpdatedTime time.Time
31 TimeAgo string
32 IsText bool
33 Children []*File
34 Host string
35}
36
37type User struct {
38 Username string
39 Email string
40 Active bool
41 Admin bool
42 CreatedAt int // timestamp
43 Reference string
44}
45
46// returns in a random order
47func getActiveUserNames() ([]string, error) {
48 rows, err := DB.Query(`SELECT username from user WHERE active is true`)
49 if err != nil {
50 return nil, err
51 }
52 var users []string
53 for rows.Next() {
54 var user string
55 err = rows.Scan(&user)
56 if err != nil {
57 return nil, err
58 }
59 users = append(users, user)
60 }
61
62 dest := make([]string, len(users))
63 perm := mathrand.Perm(len(users))
64 for i, v := range perm {
65 dest[v] = users[i]
66 }
67 return dest, nil
68}
69
70func getUserByName(username string) (*User, error) {
71 var user User
72 row := DB.QueryRow(`SELECT username, email, active, admin, created_at, reference from user WHERE username = ?`, username)
73 err := row.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference)
74 if err != nil {
75 return nil, err
76 }
77 return &user, nil
78}
79
80func getUsers() ([]User, error) {
81 rows, err := DB.Query(`SELECT username, email, active, admin, created_at, reference from user ORDER BY created_at DESC`)
82 if err != nil {
83 return nil, err
84 }
85 var users []User
86 for rows.Next() {
87 var user User
88 err = rows.Scan(&user.Username, &user.Email, &user.Active, &user.Admin, &user.CreatedAt, &user.Reference)
89 if err != nil {
90 return nil, err
91 }
92 users = append(users, user)
93 }
94 return users, nil
95}
96
97// get the user-reltaive local path from the filespath
98// NOTE -- dont use on unsafe input ( I think )
99func getLocalPath(filesPath string) string {
100 l := len(strings.Split(c.FilesDirectory, "/"))
101 return strings.Join(strings.Split(filesPath, "/")[l+1:], "/")
102}
103
104func getCreator(filePath string) string {
105 l := len(strings.Split(c.FilesDirectory, "/"))
106 r := strings.Split(filePath, "/")[l]
107 return r
108}
109
110func getIndexFiles(admin bool) ([]*File, error) { // cache this function
111 result := []*File{}
112 err := filepath.Walk(c.FilesDirectory, func(thepath string, info os.FileInfo, err error) error {
113 if err != nil {
114 log.Printf("Failure accessing a path %q: %v\n", thepath, err)
115 return err // think about
116 }
117 if !admin && info.IsDir() && info.Name() == HIDDEN_FOLDER {
118 return filepath.SkipDir
119 }
120 // make this do what it should
121 if !info.IsDir() {
122 creatorFolder := getCreator(thepath)
123 updatedTime := info.ModTime()
124 result = append(result, &File{
125 Name: getLocalPath(thepath),
126 Creator: path.Base(creatorFolder),
127 UpdatedTime: updatedTime,
128 TimeAgo: timeago(&updatedTime),
129 })
130 }
131 return nil
132 })
133 if err != nil {
134 return nil, err
135 }
136 sort.Slice(result, func(i, j int) bool {
137 return result[i].UpdatedTime.After(result[j].UpdatedTime)
138 })
139 if len(result) > 50 {
140 result = result[:50]
141 }
142 return result, nil
143} // todo clean up paths
144
145func getMyFilesRecursive(p string, creator string) ([]*File, error) {
146 result := []*File{}
147 files, err := ioutil.ReadDir(p)
148 if err != nil {
149 return nil, err
150 }
151 for _, file := range files {
152 isText := strings.HasPrefix(mime.TypeByExtension(path.Ext(file.Name())), "text")
153 fullPath := path.Join(p, file.Name())
154 localPath := getLocalPath(fullPath)
155 f := &File{
156 Name: localPath,
157 Creator: creator,
158 UpdatedTime: file.ModTime(),
159 IsText: isText,
160 Host: c.Host,
161 }
162 if file.IsDir() {
163 f.Children, err = getMyFilesRecursive(path.Join(p, file.Name()), creator)
164 }
165 result = append(result, f)
166 }
167 return result, nil
168}
169
170func createTablesIfDNE() {
171 _, err := DB.Exec(`CREATE TABLE IF NOT EXISTS user (
172 id INTEGER PRIMARY KEY NOT NULL,
173 username TEXT NOT NULL UNIQUE,
174 email TEXT NOT NULL UNIQUE,
175 password_hash TEXT NOT NULL,
176 reference TEXT NOT NULL default "",
177 active boolean NOT NULL DEFAULT false,
178 admin boolean NOT NULL DEFAULT false,
179 created_at INTEGER DEFAULT (strftime('%s', 'now'))
180);
181
182CREATE TABLE IF NOT EXISTS cookie_key (
183 value TEXT NOT NULL
184);`)
185 if err != nil {
186 log.Fatal(err)
187 }
188}
189
190// Generate a cryptographically secure key for the cookie store
191func generateCookieKeyIfDNE() []byte {
192 rows, err := DB.Query("SELECT value FROM cookie_key LIMIT 1")
193 defer rows.Close()
194 if err != nil {
195 log.Fatal(err)
196 }
197 if rows.Next() {
198 var cookie []byte
199 err := rows.Scan(&cookie)
200 if err != nil {
201 log.Fatal(err)
202 }
203 return cookie
204 } else {
205 k := make([]byte, 32)
206 _, err := io.ReadFull(rand.Reader, k)
207 if err != nil {
208 log.Fatal(err)
209 }
210 _, err = DB.Exec("insert into cookie_key values (?)", k)
211 if err != nil {
212 log.Fatal(err)
213 }
214 return k
215 }
216}
217
218func main() {
219 configPath := flag.String("c", "flounder.toml", "path to config file") // doesnt work atm
220 flag.Parse()
221 args := flag.Args()
222 if len(args) < 1 {
223 fmt.Println("expected 'admin' or 'serve' subcommand")
224 os.Exit(1)
225 }
226
227 var err error
228 c, err = getConfig(*configPath)
229 if err != nil {
230 log.Fatal(err)
231 }
232 logFile, err := os.OpenFile(c.LogFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
233 if err != nil {
234 panic(err)
235 }
236 mw := io.MultiWriter(os.Stdout, logFile)
237 log.SetOutput(mw)
238
239 if c.HttpsEnabled {
240 _, err1 := os.Stat(c.TLSCertFile)
241 _, err2 := os.Stat(c.TLSKeyFile)
242 if os.IsNotExist(err1) || os.IsNotExist(err2) {
243 log.Fatal("Keyfile or certfile does not exist.")
244 }
245 }
246
247 // Generate session cookie key if does not exist
248 DB, err = sql.Open("sqlite3", c.DBFile)
249 if err != nil {
250 log.Fatal(err)
251 }
252
253 createTablesIfDNE()
254 cookie := generateCookieKeyIfDNE()
255 SessionStore = sessions.NewCookieStore(cookie)
256
257 switch args[0] {
258 case "serve":
259 wg := new(sync.WaitGroup)
260 wg.Add(2)
261 go func() {
262 runHTTPServer()
263 wg.Done()
264 }()
265 go func() {
266 runGeminiServer()
267 wg.Done()
268 }()
269 wg.Wait()
270 case "admin":
271 runAdminCommand()
272 }
273}