func redirectToHTTPS(w http.ResponseWriter, r *http.Request, httpsOrigin *url.URL) { newurl, _ := url.Parse(r.URL.String()) newurl.Scheme = "https" if httpsOrigin != nil { newurl.Host = httpsOrigin.Host } ab.LogTrace(r).Printf("redirecting to https: %s -> %s\n", r.URL.String(), newurl.String()) http.Redirect(w, r, newurl.String(), http.StatusMovedPermanently) }
func domainEnforcerMiddleware(httpsHost, httpHost string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if httpsHost != "" && r.TLS != nil && r.Host != httpsHost { newUrl := "https://" + httpsHost + "/" + r.RequestURI ab.LogTrace(r).Printf("enforcing https domain: %s -> %s\n", r.URL.String(), newUrl) http.Redirect(w, r, newUrl, http.StatusMovedPermanently) return } if httpHost != "" && r.TLS == nil && r.Host != httpHost { newUrl := "http://" + httpHost + "/" + r.RequestURI ab.LogTrace(r).Printf("enforcing http domain: %s -> %s\n", r.URL.String(), newUrl) http.Redirect(w, r, newUrl, http.StatusMovedPermanently) return } next.ServeHTTP(w, r) }) } }
func corsPreflightHandler(baseURL, httpOrigin string) http.Handler { baseurl, err := url.Parse(baseURL) if err != nil { panic(err) } httporigin, err := url.Parse(httpOrigin) if err != nil { panic(err) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") method := r.Header.Get("Access-Control-Request-Method") headers := r.Header.Get("Access-Control-Request-Headers") ab.LogTrace(r).Printf("CORS %s %s %s", method, origin, headers) w.Header().Add("Vary", "Origin") w.Header().Add("Vary", "Access-Control-Request-Method") w.Header().Add("Vary", "Access-Control-Request-Headers") if origin == "" || method == "" { ab.Fail(http.StatusBadRequest, nil) } originurl, err := url.Parse(origin) ab.MaybeFail(http.StatusBadRequest, err) if originurl.Host != baseurl.Host && originurl.Host != httporigin.Host { ab.Fail(http.StatusForbidden, nil) } w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Methods", method) w.Header().Set("Access-Control-Allow-Headers", headers) w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Access-Control-Max-Age", "0") ab.Render(r).SetCode(http.StatusOK) }) }
func screeningService(ec *ab.EntityController) ab.Service { res := ab.EntityResource(ec, &Screening{}, ab.EntityResourceConfig{ DisablePost: true, DisableList: true, DisableGet: true, DisablePut: true, DisableDelete: true, }) res.ExtraEndpoints = func(srv *ab.Server) error { srv.Post("/api/walkthrough/:id/screening", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { wid := ab.GetParams(r).ByName("id") data := []string{} ab.MustDecode(r, &data) db := ab.GetDB(r) uid := ab.GetSession(r)["uid"] userEntity, err := ec.Load(db, "user", uid) ab.MaybeFail(http.StatusInternalServerError, err) user := userEntity.(*User) wt, err := LoadActualRevision(db, ec, wid) ab.MaybeFail(http.StatusBadRequest, err) if wt.UID != uid && !user.Admin { ab.Fail(http.StatusForbidden, nil) } if len(data) == 0 || len(data) != len(wt.Steps)-1 { ab.Fail(http.StatusBadRequest, fmt.Errorf("got %d images, expected: %d", len(data), len(wt.Steps)-1)) } screening := &Screening{ WID: wid, UID: uid, Steps: uint(len(wt.Steps) - 1), Created: time.Now(), Published: true, } err = ec.Insert(db, screening) ab.MaybeFail(http.StatusInternalServerError, err) images := map[string][]byte{} for i, d := range data { if d == "" { continue } dataurl, err := dataurl.DecodeString(d) if err != nil { ab.LogTrace(r).Printf("data url error: %s", dataurl) ab.Fail(http.StatusBadRequest, err) } if dataurl.ContentType() != "image/png" { ab.Fail(http.StatusBadRequest, errors.New("not a png")) } fn := screening.ScreenshotPath(uint(i)) images[fn] = dataurl.Data } if len(images) == 0 { ab.Fail(http.StatusBadRequest, errors.New("no images sent")) } for name, content := range images { if err := ioutil.WriteFile(name, content, 0644); err != nil { ab.LogUser(r).Println(err) } } ab.Render(r). SetCode(http.StatusCreated). JSON(screening) }), userLoggedInMiddleware) lock := map[string]chan struct{}{} var mtx sync.Mutex srv.Get("/api/walkthrough/:id/screening", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { wid := ab.GetParams(r).ByName("id") db := ab.GetDB(r) screening, err := LoadActualScreeningForWalkthrough(db, ec, wid) ab.MaybeFail(http.StatusInternalServerError, err) if screening == nil { ab.Fail(http.StatusNotFound, nil) } fn := screening.GIFPath() reply := func() { filelist := make([]string, int(screening.Steps)) for i := uint(0); i < screening.Steps; i++ { filelist[i] = "/" + screening.ScreenshotPath(i) } ab.Render(r).AddOffer("image/gif", func(w http.ResponseWriter) { f, err := os.Open(fn) ab.MaybeFail(http.StatusInternalServerError, err) defer f.Close() io.Copy(w, f) }).JSON(filelist) } if _, err := os.Stat(fn); err == nil { reply() return } // Short-circuits the gif generation process. // If the client wants JSON, there is no need // to go through the GIF generation, which is // an expensive process. if r.Header.Get("Accept") == "application/json" { reply() return } mtx.Lock() l, ok := lock[fn] if ok { mtx.Unlock() select { case <-l: reply() case <-time.After(5 * time.Second): w.Header().Set("Retry-After", "30") ab.Render(r).SetCode(http.StatusServiceUnavailable) } return } l = make(chan struct{}) lock[fn] = l mtx.Unlock() err = screening.createGIF(false) defer func() { mtx.Lock() delete(lock, fn) mtx.Unlock() }() if err != nil { if _, ok := err.(*os.PathError); ok { ab.Fail(http.StatusNotFound, err) } else { ab.Fail(http.StatusInternalServerError, err) } } close(l) reply() })) return nil } return res }