如何检查文件是否为有效图像?

I am building a web application.

On one of the pages there is an upload form, where user can upload a file. After the upload is done, I want to check on the server if the uploaded file is an image.

Is it possible to check this beyond simple file extension checking (i.e. not assuming that a *.png filename is actually a PNG image)?

For example, if I edit a JPEG image adding/editing a byte in a random place to make an invalid JPEG file, I want to detect that it is not a JPEG image anymore. I used to do such type of thing via PHP some time ago, using a GD library.

I would like to know if it is possible to do with Go?

What is usually done is checking if the file has the right magic number for the image file format you want. While this test is not super accurate, it is usually good enough. You can use code like this:

package foo

import "strings"

// image formats and magic numbers
var magicTable = map[string]string{
    "\xff\xd8\xff":      "image/jpeg",
    "\x89PNG
\x1a
": "image/png",
    "GIF87a":            "image/gif",
    "GIF89a":            "image/gif",
}

// mimeFromIncipit returns the mime type of an image file from its first few
// bytes or the empty string if the file does not look like a known file type
func mimeFromIncipit(incipit []byte) string {
    incipitStr := []byte(incipit)
    for magic, mime := range magicTable {
        if strings.HasPrefix(incipitStr, magic) {
            return mime
        }
    }

    return ""
}

The http package can do this for you:

func DetectContentType(data []byte) string

DetectContentType implements the algorithm described at http://mimesniff.spec.whatwg.org/ to determine the Content-Type of the given data. It considers at most the first 512 bytes of data. DetectContentType always returns a valid MIME type: if it cannot determine a more specific one, it returns "application/octet-stream".

Code: https://golang.org/src/net/http/sniff.go

DetectContentType is way better than a manual magic number checking. The use is simple:

clientFile, _, _ := r.FormFile("img") // or get your file from a file system
defer clientFile.Close()
buff := make([]byte, 512) // docs tell that it take only first 512 bytes into consideration
if _, err = clientFile.Read(buff); err != nil {
     fmt.Println(err) // do something with that error
     return
}

fmt.Println(http.DetectContentType(buff)) // do something based on your detection.

Using this method you need to know that you still are not guaranteed to have a correct file. So I would recommend to do some image manipulation with that file (like resize it to make sure this is really an image).