func (t *Tag) List(baseurl string) error { var err error files, err := ioutil.ReadDir(t.TagDir) for _, file := range files { // Do not care about sub directories (such as .cache) if file.IsDir() == true { continue } var f = File{} f.SetFilename(file.Name()) f.SetTag(t.Tag) f.TagDir = t.TagDir err = f.StatInfo() if err != nil { return err } err = f.DetectMIME() if err != nil { return err } if f.MediaType() == "image" { err = f.ParseExif() if err != nil { // XXX: Log this //return err } if exif.IsCriticalError(err) == false { err = f.ExtractDateTime() if err != nil { // XXX: Log this //return err } } extra := make(map[string]string) if !f.DateTime.IsZero() { extra["DateTime"] = f.DateTime.String() } f.Extra = extra } //f.ExpirationAt = t.ExpirationAt //f.ExpirationReadable = t.ExpirationReadable f.GenerateLinks(baseurl) t.Files = append(t.Files, f) } return err }
// FileTime returns the best guess of the file's creation time (or modtime). // If the file doesn't have its own metadata indication the creation time (such as in EXIF), // FileTime uses the modification time from the file system. // It there was a valid EXIF but an error while trying to get a date from it, // it logs the error and tries the other methods. func FileTime(f io.ReaderAt) (time.Time, error) { var ct time.Time defaultTime := func() (time.Time, error) { if osf, ok := f.(*os.File); ok { fi, err := osf.Stat() if err != nil { return ct, fmt.Errorf("Failed to find a modtime: stat: %v", err) } return fi.ModTime(), nil } return ct, errors.New("All methods failed to find a creation time or modtime.") } size, ok := findSize(f) if !ok { size = 256 << 10 // enough to get the EXIF } r := io.NewSectionReader(f, 0, size) var tiffErr error ex, err := exif.Decode(r) if err != nil { tiffErr = err if exif.IsShortReadTagValueError(err) { return ct, io.ErrUnexpectedEOF } if exif.IsCriticalError(err) || exif.IsExifError(err) { return defaultTime() } } ct, err = ex.DateTime() if err != nil { return defaultTime() } // If the EXIF file only had local timezone, but it did have // GPS, then lookup the timezone and correct the time. if ct.Location() == time.Local { if exif.IsGPSError(tiffErr) { log.Printf("Invalid EXIF GPS data: %v", tiffErr) return ct, nil } if lat, long, err := ex.LatLong(); err == nil { if loc := lookupLocation(latlong.LookupZoneName(lat, long)); loc != nil { if t, err := exifDateTimeInLocation(ex, loc); err == nil { return t, nil } } } else if !exif.IsTagNotPresentError(err) { log.Printf("Invalid EXIF GPS data: %v", err) } } return ct, nil }
func showEXIF(file string) { f, err := os.Open(file) if err != nil { panic(err.Error()) } defer f.Close() ex, err := exif.Decode(f) if err != nil { if exif.IsCriticalError(err) { log.Fatalf("exif.Decode, critical error: %v", err) } log.Printf("exif.Decode, warning: %v", err) } fmt.Printf("%v\n", ex) if exif.IsExifError(err) { // the error happened while decoding the EXIF sub-IFD, so as DateTime is // part of it, we have to assume (until there's a better "decode effort" // strategy in goexif) that it's not usable. return } ct, err := ex.DateTime() fmt.Printf("exif.DateTime = %v, %v\n", ct, err) }
func Upload(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { var err error f := model.File{} f.RemoteAddr = r.RemoteAddr f.UserAgent = r.Header.Get("User-Agent") // Extract the tag from the request if r.Header.Get("tag") == "" { tag := randomString(cfg.DefaultTagLength) err = f.SetTag(tag) ctx.Log.Println("Tag generated: " + f.Tag) } else { tag := r.Header.Get("tag") err = f.SetTag(tag) ctx.Log.Println("Tag specified: " + tag) } if err != nil { ctx.Log.Println(err) http.Error(w, err.Error(), http.StatusBadRequest) return } f.SetTagDir(cfg.Filedir) ctx.Log.Println("Tag directory: " + f.TagDir) // Write the request body to a temporary file err = f.WriteTempfile(r.Body, cfg.Tempdir) if err != nil { ctx.Log.Println("Unable to write tempfile: ", err) // Clean up by removing the tempfile f.ClearTemp() http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } ctx.Log.Println("Tempfile: " + f.Tempfile) ctx.Log.Println("Tempfile size: " + strconv.FormatInt(f.Bytes, 10) + " bytes") // Do not accept files that are 0 bytes if f.Bytes == 0 { ctx.Log.Println("Empty files are not allowed. Aborting.") // Clean up by removing the tempfile f.ClearTemp() http.Error(w, "No content. The file size must be more than "+ "0 bytes.", http.StatusBadRequest) return } // Calculate and verify the checksum checksum := r.Header.Get("content-sha256") if checksum != "" { ctx.Log.Println("Checksum specified: " + checksum) } err = f.VerifySHA256(checksum) ctx.Log.Println("Checksum calculated: " + f.Checksum) if err != nil { ctx.Log.Println("The specified checksum did not match") http.Error(w, "Checksum did not match", http.StatusConflict) return } // Trigger new tag t := model.Tag{} t.SetTag(f.Tag) t.SetTagDir(cfg.Filedir) if !t.TagDirExists() { if cfg.TriggerNewTag != "" { ctx.Log.Println("Executing trigger: New tag") triggerNewTagHandler(cfg.TriggerNewTag, f.Tag) } } // Create the tag directory if it does not exist err = f.EnsureTagDirectoryExists() if err != nil { ctx.Log.Println("Unable to create tag directory: ", f.TagDir) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } t.CalculateExpiration(cfg.Expiration) expired, err := t.IsExpired(cfg.Expiration) if err != nil { ctx.Log.Println(err) http.Error(w, "Internal server error", 500) return } if expired { ctx.Log.Println("The tag has expired. Aborting.") http.Error(w, "This tag has expired.", 410) return } // Extract the filename from the request fname := r.Header.Get("filename") if fname == "" { ctx.Log.Println("Filename generated: " + f.Checksum) f.SetFilename(f.Checksum) } else { ctx.Log.Println("Filename specified: " + fname) err = f.SetFilename(fname) if err != nil { ctx.Log.Println(err) http.Error(w, "Invalid filename specified. It contains illegal characters or is too short.", http.StatusBadRequest) return } } if fname != f.Filename { ctx.Log.Println("Filename sanitized: " + f.Filename) } err = f.DetectMIME() if err != nil { ctx.Log.Println("Unable to detect MIME: ", err) } else { ctx.Log.Println("MIME detected: " + f.MIME) } ctx.Log.Println("Media type: " + f.MediaType()) if f.MediaType() == "image" { err = f.ParseExif() if err != nil { ctx.Log.Println(err) } if exif.IsCriticalError(err) == false { err = f.ExtractDateTime() if err != nil { ctx.Log.Println(err) } } // iOS devices provide only one filename even when uploading // multiple images. Providing some workaround for this below. // XXX: Refactoring needed. if isWorkaroundNeeded(f.UserAgent) && !f.DateTime.IsZero() { var fname string dt := f.DateTime.Format("060102-150405") // List of filenames to modify if f.Filename == "image.jpeg" { fname = "img-" + dt + ".jpeg" } if f.Filename == "image.gif" { fname = "img-" + dt + ".gif" } if f.Filename == "image.png" { fname = "img-" + dt + ".png" } if fname != "" { ctx.Log.Println("Filename workaround triggered") ctx.Log.Println("Filename modified: " + fname) err = f.SetFilename(fname) if err != nil { ctx.Log.Println(err) } } } //err = f.GenerateThumbnail() //if err != nil { // ctx.Log.Println(err) //} extra := make(map[string]string) if !f.DateTime.IsZero() { extra["DateTime"] = f.DateTime.String() } f.Extra = extra } // Promote file from tempdir to the published tagdir f.Publish() // Clean up by removing the tempfile f.ClearTemp() err = f.StatInfo() if err != nil { ctx.Log.Println(err) http.Error(w, "Internal Server Error", 500) return } f.GenerateLinks(cfg.Baseurl) f.CreatedAt = time.Now().UTC() //f.ExpiresAt = time.Now().UTC().Add(24 * 7 * 4 * time.Hour) if cfg.TriggerUploadedFile != "" { ctx.Log.Println("Executing trigger: Uploaded file") triggerUploadedFileHandler(cfg.TriggerUploadedFile, f.Tag, f.Filename) } ctx.WorkQueue <- f headers := make(map[string]string) headers["Content-Type"] = "application/json" var status = 201 output.JSONresponse(w, status, headers, f, ctx) }
func indexEXIF(wholeRef blob.Ref, r io.Reader, mm *mutationMap) (err error) { var tiffErr error ex, err := exif.Decode(r) if err != nil { tiffErr = err if exif.IsCriticalError(err) { if exif.IsShortReadTagValueError(err) { return io.ErrUnexpectedEOF // trigger a retry with whole file } return } log.Printf("Non critical TIFF decoding error: %v", err) } defer func() { // The EXIF library panics if you access a field past // what the file contains. Be paranoid and just // recover here, instead of crashing on an invalid // EXIF file. if e := recover(); e != nil { err = errEXIFPanic } }() err = ex.Walk(exifWalkFunc(func(name exif.FieldName, tag *tiff.Tag) error { tagFmt := tagFormatString(tag) if tagFmt == "" { return nil } key := keyEXIFTag.Key(wholeRef, fmt.Sprintf("%04x", tag.Id)) numComp := int(tag.Count) if tag.Format() == tiff.StringVal { numComp = 1 } var val bytes.Buffer val.WriteString(keyEXIFTag.Val(tagFmt, numComp, "")) if tag.Format() == tiff.StringVal { str, err := tag.StringVal() if err != nil { log.Printf("Invalid EXIF string data: %v", err) return nil } if containsUnsafeRawStrByte(str) { val.WriteString(urle(str)) } else { val.WriteString(str) } } else { for i := 0; i < int(tag.Count); i++ { if i > 0 { val.WriteByte('|') } switch tagFmt { case "int": v, err := tag.Int(i) if err != nil { log.Printf("Invalid EXIF int data: %v", err) return nil } fmt.Fprintf(&val, "%d", v) case "rat": n, d, err := tag.Rat2(i) if err != nil { log.Printf("Invalid EXIF rat data: %v", err) return nil } fmt.Fprintf(&val, "%d/%d", n, d) case "float": v, err := tag.Float(i) if err != nil { log.Printf("Invalid EXIF float data: %v", err) return nil } fmt.Fprintf(&val, "%v", v) default: panic("shouldn't get here") } } } valStr := val.String() mm.Set(key, valStr) return nil })) if err != nil { return } if exif.IsGPSError(tiffErr) { log.Printf("Invalid EXIF GPS data: %v", tiffErr) return nil } if lat, long, err := ex.LatLong(); err == nil { if math.Abs(long) > 180.0 || math.Abs(lat) > 90.0 { log.Printf("Long, lat outside allowed range: %v, %v", long, lat) return nil } // index 7 places fixed precision (~10mm worst case at equator) // http://stackoverflow.com/a/1947615/114581 mm.Set(keyEXIFGPS.Key(wholeRef), keyEXIFGPS.Val(fmt.Sprintf("%.7f", lat), fmt.Sprintf("%.7f", long))) } else if !exif.IsTagNotPresentError(err) { log.Printf("Invalid EXIF GPS data: %v", err) } return nil }