package handlers import ( "fmt" "io" "mime" "net/http" "regexp" "strings" "time" "github.com/google/uuid" "github.com/pkg/errors" "github.com/fiskerinc/cloud-services/pkg/common" "github.com/fiskerinc/cloud-services/pkg/s3" u "github.com/fiskerinc/cloud-services/pkg/utils" "github.com/fiskerinc/cloud-services/pkg/validator" "otaupdate/messages" "otaupdate/services" "otaupdate/utils" "github.com/fiskerinc/cloud-services/pkg/loggerdataresp" ) const fileFieldName string = "file" var reURLSafe *regexp.Regexp func init() { reURLSafe, _ = regexp.Compile(`[^\w\d\-\_\.]`) } // HandleUpdateManifestFileAdd godoc // @Summary Add update manifest // @Description Upload update manifest // @Accept json // @Produce json // @Param Authorization header string false "Bearer " // @Param Api-Key header string false "" // @Param manifest_ecu_id formData int true "Manifest ECU id" // @Param version formData string true "ECU file version" // @Param offset formData string true "ECU file offset" // @Param checksum formData string true "ECU file checksum" // @Param type formData string false "ECU file type" // @Param order formData string false "ECU file order" // @Param file formData file true "Update file" // @Success 200 {object} common.UpdateManifestFile // @Failure 400 {object} common.JSONError "Bad request" // @Failure 401 {object} common.JSONError "Unauthorized" // @Failure 503 {object} common.JSONError "Service unavailable" // @Router /manifestfile [post] func HandleUpdateManifestFileAdd(w http.ResponseWriter, r *http.Request) { info, manifestfile, err := parseUpdateManifestAddRequest(r) if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) { return } result, encryptor, err := uploadFile(info) if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) { return } err = saveUpdateManifestFile(manifestfile, result, info) if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) { return } err = encryptor.SaveFileKey() if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) { return } u.RespJSON(w, http.StatusOK, manifestfile) } func parseUpdateManifestAddRequest(r *http.Request) (*u.FileInfo, *common.UpdateManifestFile, error) { mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) if err != nil { return nil, nil, errors.WithStack(err) } if !strings.HasPrefix(mediaType, "multipart/") { return nil, nil, errors.New(messages.RequiresMultipart) } if len(params["boundary"]) > 70 { return nil, nil, errors.New(messages.BoundaryTooLong) } parser, file := utils.GetUpdateManifestFileFormParser() info, err := u.FindFilePart(r, params["boundary"], fileFieldName, parser) if err != nil { return nil, nil, err } if info.Part == nil { return nil, nil, errors.New(messages.RequiresFile) } err = validator.ValidateStruct(file) if err != nil { return nil, nil, err } return info, file, nil } func saveUpdateManifestFile(um *common.UpdateManifestFile, link *common.JSONLink, info *u.FileInfo) error { um.FileID = info.FileID um.Filename = info.Filename um.URL = link.Link um.FileSize = info.FileSize um.WriteRegion.Length = info.OrigFileSize err := validator.ValidateStruct(um) if err != nil { return err } _, err = services.GetDB().GetUpdateManifests().FileInsert(um) return err } func uploadFile(info *u.FileInfo) (link *common.JSONLink, encryptor *utils.FileEncryptor, err error) { encryptor, err = utils.NewEncryptor() if err != nil { return nil, nil, errors.WithStack(err) } defer encryptor.Close() pReader, pWriter := io.Pipe() defer pReader.Close() defer info.Part.Close() go u.ChunkFilePart(pWriter, info, encryptor.Encrypt) url, err := putFile(pReader, urlSafeStr(info.Filename), "application/octet-stream") if err != nil { return } info.FileID = encryptor.FileID p := u.LinkResult(url, time.Now().Unix()) link = &p return } func putFile(reader io.Reader, filename string, contentType string) (string, error) { uid, err := uuid.NewRandom() if err != nil { return "", errors.WithStack(err) } key := fmt.Sprintf("%s/%s", uid, filename) url, err := s3.GetS3().PutBucket(key, reader, contentType) if err != nil { return "", errors.WithStack(err) } return url, nil } func urlSafeStr(value string) string { return reURLSafe.ReplaceAllLiteralString(value, "") }