Commit a3b1b28
Changed files (1)
serverHandlers.go
@@ -11,8 +11,14 @@ import (
"path/filepath"
"strconv"
"strings"
+ "sync"
)
+// userTests is a map that contains arrays of client tests
+// this map is updated when a client requests a score
+var userTests map[string][]ClientTest
+var userTestsLock = sync.RWMutex{}
+
// buildRecordArray builds an array of Records based on the parameter criteria
func buildRecordArray(numQuestions int, categories []int) []Record {
var keyIndex, valueIndex int
@@ -174,6 +180,83 @@ func handleCategoryQueries(writer http.ResponseWriter, request *http.Request) {
}
}
+// loadTestFile loads a user's test
+// in a parallel-safe way
+func loadTestFile(file *os.File, username string) error {
+ buffer, err := ioutil.ReadAll(file)
+ if err != nil {
+ return fmt.Errorf("Could not read test file: %+v\n", err.Error())
+ }
+ var test ClientTest
+ err = json.Unmarshal(buffer, &test)
+ if err != nil {
+ return fmt.Errorf("Could not parse test file %s: %+v\n", file.Name(), err.Error())
+ }
+ userTestsLock.Lock()
+ defer userTestsLock.Unlock()
+ if userTests[username] == nil {
+ userTests[username] = []ClientTest{}
+ }
+ userTests[username] = append(userTests[username], test)
+ return nil
+}
+
+// validateUserFile validates the existence of a user's test before attempting
+// to load it
+func validateUserFile(path string, fileInfo os.FileInfo, err error) error {
+ if !strings.Contains(path, serverConfig.USER_TESTS) {
+ return fmt.Errorf("Suspected malicious file access attempt: %s", path)
+ }
+ file, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ fileStats, err := file.Stat()
+ if err != nil {
+ return err
+ }
+ if fileStats.Mode().IsRegular() {
+ directories := strings.Split(filepath.Dir(path), string(filepath.Separator))
+ username := directories[len(directories)-1]
+ err = loadTestFile(file, username)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// handleScoreRequests gives the client the scores for the specified user name
+func handleScoreRequests(writer http.ResponseWriter, request *http.Request) {
+ switch request.Method {
+ case "POST":
+ log.Printf("Client attempted to POST to /%s/test/score: %+v\tvia %+v", API_ROOT, request.RemoteAddr, request.UserAgent())
+ case "GET":
+ log.Printf("Client requested test score: %+v\tvia %+v", request.RemoteAddr, request.UserAgent())
+ username := request.FormValue("username")
+ if username != "" {
+ var err error
+ resultsFilePath := filepath.Join(serverConfig.USER_TESTS, username)
+ if err = filepath.Walk(resultsFilePath, validateUserFile); err != nil {
+ log.Printf("Failed to load user test due to error: %+v", err)
+ http.Error(writer, "Failed to load tests.", http.StatusInternalServerError)
+ return
+ }
+ userTestsLock.Lock()
+ defer userTestsLock.Unlock()
+ data, err := json.Marshal(userTests[username])
+ if err != nil {
+ log.Printf("Error for %s while marshaling: %+v", err, resultsFilePath)
+ }
+ writer.Write(data)
+ delete(userTests, username)
+ } else {
+ http.Error(writer, "No user specified.", http.StatusBadRequest)
+ }
+ }
+}
+
type ServerHandler struct {
Request string
HandleFunction func(writer http.ResponseWriter, request *http.Request)
@@ -186,8 +269,12 @@ func init() {
API_ROOT + "/test": ServerHandler{
Request: API_ROOT + "/test",
HandleFunction: handleTestQueries},
+ API_ROOT + "/test/score": ServerHandler{
+ Request: API_ROOT + "/test/score",
+ HandleFunction: handleScoreRequests},
API_ROOT + "/questions/categories": ServerHandler{
Request: API_ROOT + "/questions/categories",
HandleFunction: handleCategoryQueries},
}
+ userTests = map[string][]ClientTest{}
}