Author: Adam Evyčędo <git@apiote.xyz>
check duplicates in link shorts
router.go | 145 ++++++++++++++++++++++++++++++++++----------------------
diff --git a/router.go b/router.go index d90ba8d040ec0ea9b3c7cd176dbb298bd1984bda..740814420b731873128426f2d91c86b250bf0cdd 100644 --- a/router.go +++ b/router.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "bytes" "embed" "fmt" @@ -156,15 +157,17 @@ t, err := template.ParseFS(templatesFS, "templates/"+name+".html", "templates/aside.html", "templates/header.html", "templates/nav.html", "templates/head.html", "templates/head_program.html") if err != nil { log.Println(err) if os.IsNotExist(err) { - renderStatus(w, 501, acceptLanguage) + renderStatus(w, http.StatusNotImplemented, acceptLanguage) return } + w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "Error 500") return } err = t.Execute(w, data) if err != nil { log.Println(err) + w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "Error 500") return } @@ -182,7 +185,7 @@ if path[0] == "" { showHtml(w, "index", nil, acceptLanguage) } if path[0] != "" { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) } } @@ -216,7 +219,7 @@ acceptLanguage := r.Header.Get("Accept-Language") path := strings.Split(r.URL.Path[1:], "/") programs, err := readPrograms() if err != nil { - renderStatus(w, 500, acceptLanguage) + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } var ( @@ -236,17 +239,17 @@ name := path[1] program = programs[name] version, present = program.Versions[path[2]] } else { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) return } if !present { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) return } file, err := programsFS.Open("programs/" + version.Description) if err != nil { if os.IsNotExist(err) { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) return } else { log.Println(err) @@ -283,22 +286,22 @@ acceptLanguage := r.Header.Get("Accept-Language") accept := r.Header["Accept"][0] path := strings.Split(r.URL.Path[1:], "/") if path[1] == "" { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) } else { file, err := staticFS.Open("static/" + path[1] + ".asc") defer file.Close() if err != nil && os.IsNotExist(err) { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) return } else if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } key, err := ioutil.ReadAll(file) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } w.Header().Set("Vary", "Accept") @@ -418,11 +421,11 @@ acceptLanguage := r.Header.Get("Accept-Language") file, err := os.Open(filepath.Join(dataDir, "holidays.dirty")) if err != nil { if os.IsNotExist(err) { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) return } else { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } } @@ -432,17 +435,17 @@ err = dirty.LoadStruct(file, &holidays) if err != nil { log.Println(err) log.Println(holidays) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } t, err := template.ParseFS(templatesFS, "templates/calendar.ics") if err != nil { log.Println(err) if os.IsNotExist(err) { - renderStatus(w, 501, acceptLanguage) + renderStatus(w, http.StatusNotImplemented, acceptLanguage) return } - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } thisYear := time.Now().Year() @@ -453,7 +456,7 @@ w.Header().Set("Content-Type", "text/calendar") err = t.Execute(w, holidays) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } } @@ -482,68 +485,97 @@ } else { id = strings.ToUpper(id) } if url == "" { - renderStatus(w, 400, acceptLanguage) + renderStatus(w, http.StatusBadRequest, acceptLanguage) return } hash, err := ioutil.ReadFile(filepath.Join(stateDir, "password")) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } passMatch, err := ComparePasswordAndHash(password, string(hash)) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } if !passMatch { - renderStatus(w, 403, acceptLanguage) + renderStatus(w, http.StatusForbidden, acceptLanguage) return } - // todo check duplicates - - file, err := os.OpenFile(filepath.Join(stateDir, "s.toml"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) - defer file.Close() + file, err := os.OpenFile(filepath.Join(stateDir, "s.toml"), os.O_RDONLY|os.O_CREATE, 0600) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } - _, err = file.WriteString(id + " = " + url + "\n") + defer file.Close() + + shorts := map[string]string{} + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.Split(scanner.Text(), " = ") + shorts[line[0]] = line[1] + } + if err := scanner.Err(); err != nil { + log.Println(err) + renderStatus(w, http.StatusInternalServerError, acceptLanguage) + return + } + file.Close() + + if _, ok := shorts[id]; ok { + renderStatus(w, http.StatusConflict, acceptLanguage) + return + } + shorts[id] = url + + file, err = os.OpenFile(filepath.Join(stateDir, "s.toml"), os.O_WRONLY|os.O_TRUNC, 0600) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } + defer file.Close() + for shortID, shortURL := range shorts { + _, err = file.WriteString(shortID + " = " + shortURL + "\n") + if err != nil { + log.Println(err) + renderStatus(w, http.StatusInternalServerError, acceptLanguage) + return + } + } + w.Header().Add("X-Redirection", "https://apiote.xyz/s/"+id) - w.WriteHeader(201) + w.WriteHeader(http.StatusCreated) showHtml(w, "s201", Redirection{id, url, "https://apiote.xyz/s/" + id}, acceptLanguage) } } else if len(path) != 2 { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) } else { id := strings.ToUpper(path[1]) - data, err := ioutil.ReadFile(filepath.Join(stateDir, "s.toml")) + file, err := os.Open(filepath.Join(stateDir, "s.toml")) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } - for _, line := range strings.Split(string(data), "\n") { - if line == "" { - break + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.Split(scanner.Text(), " = ") + if len(line) < 2 { + continue } - redirection := strings.Split(line, "=") - redirectionID := strings.Trim(redirection[0], " ") - redirectionURL := strings.Trim(redirection[1], " ") - if redirectionID == id { - http.Redirect(w, r, redirectionURL, http.StatusMovedPermanently) + if line[0] == id { + http.Redirect(w, r, line[1], http.StatusMovedPermanently) return } } - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) } } } @@ -554,27 +586,28 @@ acceptLanguage := r.Header.Get("Accept-Language") path := strings.Split(r.URL.Path[1:], "/") if len(path) == 2 && path[1] != "" { id := strings.ToUpper(path[1]) - data, err := ioutil.ReadFile(filepath.Join(stateDir, "s.toml")) + file, err := os.Open(filepath.Join(stateDir, "s.toml")) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } - for _, line := range strings.Split(string(data), "\n") { - if line == "" { - break + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.Split(scanner.Text(), " = ") + if len(line) < 2 { + continue } - redirection := strings.Split(line, "=") - redirectionID := strings.Trim(redirection[0], " ") - redirectionURL := strings.Trim(redirection[1], " ") - if redirectionID == id { - showHtml(w, "short", Redirection{redirectionID, redirectionURL, "https://apiote.xyz/short/" + redirectionID}, acceptLanguage) + if line[0] == id { + showHtml(w, "short", Redirection{line[0], line[1], "https://apiote.xyz/short/" + line[0]}, acceptLanguage) return } } - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) } else { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) } } } @@ -583,16 +616,16 @@ func wkd(w http.ResponseWriter, r *http.Request) { acceptLanguage := r.Header.Get("Accept-Language") path := strings.Split(r.URL.Path[1:], "/") if path[3] == "" { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) } else { file, err := staticFS.Open("static/" + path[3] + ".pgp") defer file.Close() if err != nil && os.IsNotExist(err) { - renderStatus(w, 404, acceptLanguage) + renderStatus(w, http.StatusNotFound, acceptLanguage) return } else if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) return } w.Header().Set("Content-Type", "application/pgp-key") @@ -600,7 +633,7 @@ w.Header().Set("Access-Control-Allow-Origin", "*") _, err = io.Copy(w, file) if err != nil { log.Println(err) - fmt.Fprint(w, "Error 500") + renderStatus(w, http.StatusInternalServerError, acceptLanguage) } } }