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