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