journal/main.go

442 lines
13 KiB
Go
Raw Permalink Normal View History

2023-11-09 18:11:16 +00:00
// citrusNikOs' journal -- минималистичный сервер для веб-журнала с расписанием.
//
// Copyright (c) 2023 Nikita Osokin.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package main
import (
"fmt"
"io"
"os"
"sync"
"net/http"
"time"
"strconv"
)
var schedFileRWMutex sync.RWMutex
var classesRWMutexes map[string]*sync.RWMutex
var classesNames []string
var error500Text []byte
2023-11-09 18:11:16 +00:00
func isValidClass(class string) bool {
for i := 0; i < len(classesNames); i++ {
if classesNames[i] == class {
return true
}
}
return false
}
func getCurrentDay() uint64 {
return uint64(time.Now().Unix()) / 86400
}
func isValidDay(day string) bool {
if len(day) == 0 || (day[0] == '0' && len(day) != 1) {
return false
}
for i := 0; i < len(day); i++ {
if day[i] < '0' || day[i] > '9' {
return false
}
}
return true
}
func read8BytesBE(reader io.Reader, file *os.File) (uint64, error) {
var result uint64
var err error
var count int
buffer := make([]byte, 8)
count, err = reader.Read(buffer)
if count != 8 {
return 0, err
}
for i := 0; i < 8; i++ {
result <<= 8
result |= uint64(buffer[i])
}
_, err = (*file).Write(buffer)
if err != nil {
return 0, err
}
return result, nil
}
func read8BytesBENoCopy(reader io.Reader) (uint64, error) {
var result uint64
var err error
var count int
buffer := make([]byte, 8)
count, err = reader.Read(buffer)
if count != 8 {
return 0, err
}
for i := 0; i < 8; i++ {
result <<= 8
result |= uint64(buffer[i])
}
return result, nil
}
func writeUint64BE(writer io.Writer, num uint64) error {
buffer := make([]byte, 8)
bufferI := 0
for i := 56; i >= 0; i -= 8 {
buffer[bufferI] = uint8((num >> i) & 0xFF)
bufferI++
}
_, err := writer.Write(buffer)
if err != nil {
return err
}
return nil
}
func respondWithFile(writer http.ResponseWriter, filename string) {
2023-11-09 18:11:16 +00:00
file, err := os.Open(filename)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
writer.Write(error500Text)
2023-11-09 18:11:16 +00:00
return
}
stat, err := file.Stat()
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
writer.Write(error500Text)
file.Close()
2023-11-09 18:11:16 +00:00
return
}
buffer := make([]byte, stat.Size())
_, err = file.Read(buffer);
2023-11-09 18:11:16 +00:00
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
writer.Write(error500Text)
file.Close()
2023-11-09 18:11:16 +00:00
return
}
writer.Write(buffer)
file.Close()
2023-11-09 18:11:16 +00:00
}
func handleRootAnd404(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path != "/" {
writer.WriteHeader(http.StatusNotFound)
respondWithFile(writer, "404.html")
2023-11-09 18:11:16 +00:00
} else {
respondWithFile(writer, "index.html")
2023-11-09 18:11:16 +00:00
}
}
func handleGetClassesList(writer http.ResponseWriter, request *http.Request) {
err := writeUint64BE(writer, uint64(len(classesNames)))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
for i := 0; i < len(classesNames); i++ {
err = writeUint64BE(writer, uint64(len(classesNames[i])))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
_, err = writer.Write([]uint8(classesNames[i]))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
}
}
func handleEditPage(writer http.ResponseWriter, request *http.Request) {
if request.Method == http.MethodPost {
queryValues := request.URL.Query()
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
writer.WriteHeader(http.StatusBadRequest)
return
}
var day string
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
day = strconv.FormatUint(getCurrentDay(), 10)
} else {
day = queryValues["d"][0]
}
passwordLen, err := read8BytesBENoCopy(request.Body);
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
return
}
passwordFile, err := os.Open("classes/" + queryValues["c"][0] + "/password")
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
return
}
passwordFileInfo, err := passwordFile.Stat()
if err != nil {
passwordFile.Close()
writer.WriteHeader(http.StatusInternalServerError)
return
}
if uint64(passwordFileInfo.Size() - 1) != passwordLen {
passwordFile.Close()
writer.WriteHeader(http.StatusUnauthorized)
return
}
userPassword := make([]byte, passwordLen)
passwordCount, err := request.Body.Read(userPassword)
if uint64(passwordCount) != passwordLen || err != nil {
passwordFile.Close()
writer.WriteHeader(http.StatusBadRequest)
return
}
password := make([]byte, passwordLen)
passwordCount, err = passwordFile.Read(password)
if uint64(passwordCount) != passwordLen || err != nil {
passwordFile.Close()
writer.WriteHeader(http.StatusInternalServerError)
return
}
for i := uint64(0); i < passwordLen; i++ {
if password[i] != userPassword[i] {
passwordFile.Close()
writer.WriteHeader(http.StatusUnauthorized)
return
}
}
tmpSchedFile, err := os.CreateTemp("", "sched")
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
return
}
layoutID, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil || layoutID != 0 {
writer.WriteHeader(http.StatusBadRequest)
os.Remove(tmpSchedFile.Name())
return
}
lessonsCount, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil || lessonsCount > 7 {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
buffer := make([]byte, 1)
lastNumber := uint64(0)
isFirst := true
for i := uint64(0); i < lessonsCount; i++ {
number, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
if !isFirst && number <= lastNumber {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
isFirst = false
lastNumber = number
nameLen, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
for j := uint64(0); j < nameLen; j++ {
beenRead, _ := request.Body.Read(buffer)
_, err := (*tmpSchedFile).Write(buffer)
if beenRead == 0 || err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
}
descrLen, err := read8BytesBE(request.Body, tmpSchedFile)
if err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
for j := uint64(0); j < descrLen; j++ {
beenRead, _ := request.Body.Read(buffer)
_, err := (*tmpSchedFile).Write(buffer)
if beenRead == 0 || err != nil {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
}
}
beenRead, err := request.Body.Read(buffer)
if beenRead != 0 {
writer.WriteHeader(http.StatusBadRequest)
os.Remove((*tmpSchedFile).Name())
return
}
classesRWMutexes[queryValues["c"][0]].Lock()
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
os.Remove(schedFileName)
os.Rename((*tmpSchedFile).Name(), schedFileName)
classesRWMutexes[queryValues["c"][0]].Unlock()
} else {
respondWithFile(writer, "editpage.html")
2023-11-09 18:11:16 +00:00
}
}
2023-11-09 18:11:16 +00:00
func handleSchedule(writer http.ResponseWriter, request *http.Request) {
respondWithFile(writer, "schedule.html")
2023-11-09 18:11:16 +00:00
}
/* TODO: возможно стоит отправлять что-то кроме просто ошибки 500 */
func handleScheduleGet(writer http.ResponseWriter, request *http.Request) {
queryValues := request.URL.Query()
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
writer.WriteHeader(http.StatusBadRequest)
return
}
var day string
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
day = strconv.FormatUint(getCurrentDay(), 10)
} else {
day = queryValues["d"][0]
}
classesRWMutexes[queryValues["c"][0]].RLock()
defer classesRWMutexes[queryValues["c"][0]].RUnlock()
i := 0
var schedFile *os.File
var err error
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
for schedFile, err = os.Open(schedFileName); err != nil; schedFile, err = os.Open(schedFileName) {
if i == 1000 {
return
}
i++
}
_, err = io.Copy(writer, schedFile)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
schedFile.Close()
}
// TODO: Переформатировать эту функцию.
2023-11-09 18:11:16 +00:00
func main() {
var err error
error500File, err := os.Open("500.html")
if err != nil {
fmt.Printf("[!!!]Не удалось открыть файл 500.html.\n")
return
}
error500FileStat, err := error500File.Stat()
if err != nil {
error500File.Close()
fmt.Printf("[!!!]Не удалось прочитать информацию о файле 500.html.\n")
return
}
error500Text = make([]byte, error500FileStat.Size())
_, err = error500File.Read(error500Text);
if err != nil {
error500File.Close()
fmt.Printf("[!!!]Не удалось прочитать файл 500.html.\n")
return
}
error500File.Close()
2023-11-09 18:11:16 +00:00
classesRWMutexes = make(map[string]*sync.RWMutex)
var classesDir *os.File
var classes []os.DirEntry
classesDir, err = os.Open("classes")
if err != nil {
fmt.Printf("[!!!]Не удалось открыть директорию classes.\n")
return
}
classes, err = classesDir.ReadDir(0)
classesDir.Close()
2023-11-09 18:11:16 +00:00
if err != nil {
fmt.Printf("[!!!]Не удалось прочитать содержимое директории classes.\n")
return
}
classesNames = make([]string, len(classes))
for i := 0; i < len(classes); i++ {
if !classes[i].IsDir() {
fmt.Printf("[!!!]Директория classes должна содержать только директории.\n")
return
}
classesNames[i] = classes[i].Name()
classesRWMutexes[classes[i].Name()] = new(sync.RWMutex)
}
classes = nil
2023-11-09 18:11:16 +00:00
http.HandleFunc("/", handleRootAnd404)
http.HandleFunc("/getClassesList", handleGetClassesList)
http.HandleFunc("/schedule", handleSchedule)
http.HandleFunc("/scheduleGet", handleScheduleGet)
http.HandleFunc("/editpage", handleEditPage)
http.ListenAndServe(":62314", nil)
}