Source File
fs.go
Belonging Package
net/http
package http
import (
)
func ( error, string) error {
if os.IsNotExist() || os.IsPermission() {
return
}
:= strings.Split(, string(filepath.Separator))
for := range {
if [] == "" {
continue
}
, := os.Stat(strings.Join([:+1], string(filepath.Separator)))
if != nil {
return
}
if !.IsDir() {
return fs.ErrNotExist
}
}
return
}
func ( Dir) ( string) (File, error) {
if filepath.Separator != '/' && strings.ContainsRune(, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
}
:= string()
if == "" {
= "."
}
:= filepath.Join(, filepath.FromSlash(path.Clean("/"+)))
, := os.Open()
if != nil {
return nil, mapDirOpenError(, )
}
return , nil
}
type FileSystem interface {
Open(name string) (File, error)
}
type File interface {
io.Closer
io.Reader
io.Seeker
Readdir(count int) ([]fs.FileInfo, error)
Stat() (fs.FileInfo, error)
}
type anyDirs interface {
len() int
name(i int) string
isDir(i int) bool
}
type fileInfoDirs []fs.FileInfo
func ( fileInfoDirs) () int { return len() }
func ( fileInfoDirs) ( int) bool { return [].IsDir() }
func ( fileInfoDirs) ( int) string { return [].Name() }
type dirEntryDirs []fs.DirEntry
func ( dirEntryDirs) () int { return len() }
func ( dirEntryDirs) ( int) bool { return [].IsDir() }
func ( dirEntryDirs) ( int) string { return [].Name() }
var anyDirs
var error
if , := .(fs.ReadDirFile); {
var dirEntryDirs
, = .ReadDir(-1)
=
} else {
var fileInfoDirs
, = .Readdir(-1)
=
}
if != nil {
logf(, "http: error reading directory: %v", )
Error(, "Error reading directory", StatusInternalServerError)
return
}
sort.Slice(, func(, int) bool { return .name() < .name() })
.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(, "<pre>\n")
for , := 0, .len(); < ; ++ {
:= .name()
if .isDir() {
+= "/"
var errNoOverlap = errors.New("invalid range: failed to overlap")
func ( ResponseWriter, *Request, string, time.Time, func() (int64, error), io.ReadSeeker) {
setLastModified(, )
, := checkPreconditions(, , )
if {
return
}
:= StatusOK
var [sniffLen]byte
, := io.ReadFull(, [:])
= DetectContentType([:])
, := .Seek(0, io.SeekStart) // rewind to output whole file
if != nil {
Error(, "seeker can't seek", StatusInternalServerError)
return
}
}
.Header().Set("Content-Type", )
} else if len() > 0 {
= [0]
}
, := ()
if != nil {
Error(, .Error(), StatusInternalServerError)
return
}
:=
var io.Reader =
if >= 0 {
, := parseRange(, )
if != nil {
if == errNoOverlap {
.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", ))
}
Error(, .Error(), StatusRequestedRangeNotSatisfiable)
return
}
= nil
}
switch {
:= [0]
if , := .Seek(.start, io.SeekStart); != nil {
Error(, .Error(), StatusRequestedRangeNotSatisfiable)
return
}
= .length
= StatusPartialContent
.Header().Set("Content-Range", .contentRange())
case len() > 1:
= rangesMIMESize(, , )
= StatusPartialContent
, := io.Pipe()
:= multipart.NewWriter()
.Header().Set("Content-Type", "multipart/byteranges; boundary="+.Boundary())
=
defer .Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
go func() {
for , := range {
, := .CreatePart(.mimeHeader(, ))
if != nil {
.CloseWithError()
return
}
if , := .Seek(.start, io.SeekStart); != nil {
.CloseWithError()
return
}
if , := io.CopyN(, , .length); != nil {
.CloseWithError()
return
}
}
.Close()
.Close()
}()
}
.Header().Set("Accept-Ranges", "bytes")
if .Header().Get("Content-Encoding") == "" {
.Header().Set("Content-Length", strconv.FormatInt(, 10))
}
}
.WriteHeader()
if .Method != "HEAD" {
io.CopyN(, , )
}
}
for := + 1; < len(); ++ {
:= []
case == 0x21 || >= 0x23 && <= 0x7E || >= 0x80:
case == '"':
return [:+1], [+1:]
default:
return "", ""
}
}
return "", ""
}
func (, string) bool {
return strings.TrimPrefix(, "W/") == strings.TrimPrefix(, "W/")
}
type condResult int
const (
condNone condResult = iota
condTrue
condFalse
)
func ( ResponseWriter, *Request) condResult {
:= .Header.Get("If-Match")
if == "" {
return condNone
}
for {
= textproto.TrimString()
if len() == 0 {
break
}
if [0] == ',' {
= [1:]
continue
}
if [0] == '*' {
return condTrue
}
, := scanETag()
if == "" {
break
}
if etagStrongMatch(, .Header().get("Etag")) {
return condTrue
}
=
}
return condFalse
}
func ( *Request, time.Time) condResult {
:= .Header.Get("If-Unmodified-Since")
if == "" || isZeroTime() {
return condNone
}
, := ParseTime()
if != nil {
return condNone
}
= .Truncate(time.Second)
if .Before() || .Equal() {
return condTrue
}
return condFalse
}
func ( ResponseWriter, *Request) condResult {
:= .Header.get("If-None-Match")
if == "" {
return condNone
}
:=
for {
= textproto.TrimString()
if len() == 0 {
break
}
if [0] == ',' {
= [1:]
continue
}
if [0] == '*' {
return condFalse
}
, := scanETag()
if == "" {
break
}
if etagWeakMatch(, .Header().get("Etag")) {
return condFalse
}
=
}
return condTrue
}
func ( *Request, time.Time) condResult {
if .Method != "GET" && .Method != "HEAD" {
return condNone
}
:= .Header.Get("If-Modified-Since")
if == "" || isZeroTime() {
return condNone
}
, := ParseTime()
if != nil {
return condNone
= .Truncate(time.Second)
if .Before() || .Equal() {
return condFalse
}
return condTrue
}
func ( ResponseWriter, *Request, time.Time) condResult {
if .Method != "GET" && .Method != "HEAD" {
return condNone
}
:= .Header.get("If-Range")
if == "" {
return condNone
}
, := scanETag()
if != "" {
if etagStrongMatch(, .Header().Get("Etag")) {
return condTrue
} else {
return condFalse
}
func ( time.Time) bool {
return .IsZero() || .Equal(unixEpochTime)
}
func ( ResponseWriter, time.Time) {
if !isZeroTime() {
.Header().Set("Last-Modified", .UTC().Format(TimeFormat))
}
}
:= .Header()
delete(, "Content-Type")
delete(, "Content-Length")
if .Get("Etag") != "" {
delete(, "Last-Modified")
}
.WriteHeader(StatusNotModified)
}
:= checkIfMatch(, )
if == condNone {
= checkIfUnmodifiedSince(, )
}
if == condFalse {
.WriteHeader(StatusPreconditionFailed)
return true, ""
}
switch checkIfNoneMatch(, ) {
case condFalse:
if .Method == "GET" || .Method == "HEAD" {
writeNotModified()
return true, ""
} else {
.WriteHeader(StatusPreconditionFailed)
return true, ""
}
case condNone:
if checkIfModifiedSince(, ) == condFalse {
writeNotModified()
return true, ""
}
}
= .Header.get("Range")
if != "" && checkIfRange(, , ) == condFalse {
= ""
}
return false,
}
func ( ResponseWriter, *Request, FileSystem, string, bool) {
const = "/index.html"
if strings.HasSuffix(.URL.Path, ) {
localRedirect(, , "./")
return
}
, := .Open()
if != nil {
, := toHTTPError()
Error(, , )
return
}
defer .Close()
, := .Stat()
if != nil {
, := toHTTPError()
Error(, , )
return
}
if == "" || [len()-1] != '/' {
localRedirect(, , path.Base()+"/")
return
}
if .IsDir() {
if checkIfModifiedSince(, .ModTime()) == condFalse {
writeNotModified()
return
}
setLastModified(, .ModTime())
dirList(, , )
return
}
func ( error) ( string, int) {
if os.IsNotExist() {
return "404 page not found", StatusNotFound
}
if os.IsPermission() {
return "403 Forbidden", StatusForbidden
return "500 Internal Server Error", StatusInternalServerError
}
func ( ResponseWriter, *Request, string) {
if := .URL.RawQuery; != "" {
+= "?" +
}
.Header().Set("Location", )
.WriteHeader(StatusMovedPermanently)
}
func ( ResponseWriter, *Request, string) {
Error(, "invalid URL path", StatusBadRequest)
return
}
, := filepath.Split()
serveFile(, , Dir(), , false)
}
func ( string) bool {
if !strings.Contains(, "..") {
return false
}
for , := range strings.FieldsFunc(, isSlashRune) {
if == ".." {
return true
}
}
return false
}
func ( rune) bool { return == '/' || == '\\' }
type fileHandler struct {
root FileSystem
}
type ioFS struct {
fsys fs.FS
}
type ioFile struct {
file fs.File
}
func ( ioFS) ( string) (File, error) {
if == "/" {
= "."
} else {
= strings.TrimPrefix(, "/")
}
, := .fsys.Open()
if != nil {
return nil,
}
return ioFile{}, nil
}
func ( ioFile) () error { return .file.Close() }
func ( ioFile) ( []byte) (int, error) { return .file.Read() }
func ( ioFile) () (fs.FileInfo, error) { return .file.Stat() }
var errMissingSeek = errors.New("io.File missing Seek method")
var errMissingReadDir = errors.New("io.File directory missing ReadDir method")
func ( ioFile) ( int64, int) (int64, error) {
, := .file.(io.Seeker)
if ! {
return 0, errMissingSeek
}
return .Seek(, )
}
func ( ioFile) ( int) ([]fs.DirEntry, error) {
, := .file.(fs.ReadDirFile)
if ! {
return nil, errMissingReadDir
}
return .ReadDir()
}
func ( ioFile) ( int) ([]fs.FileInfo, error) {
, := .file.(fs.ReadDirFile)
if ! {
return nil, errMissingReadDir
}
var []fs.FileInfo
for {
, := .ReadDir( - len())
for , := range {
, := .Info()
func ( fs.FS) FileSystem {
return ioFS{}
}
func ( FileSystem) Handler {
return &fileHandler{}
}
func ( *fileHandler) ( ResponseWriter, *Request) {
:= .URL.Path
if !strings.HasPrefix(, "/") {
= "/" +
.URL.Path =
}
serveFile(, , .root, path.Clean(), true)
}
func ( string, int64) ([]httpRange, error) {
if == "" {
return nil, nil // header not present
}
const = "bytes="
if !strings.HasPrefix(, ) {
return nil, errors.New("invalid range")
}
var []httpRange
:= false
for , := range strings.Split([len():], ",") {
= textproto.TrimString()
if == "" {
continue
}
:= strings.Index(, "-")
if < 0 {
return nil, errors.New("invalid range")
}
, := textproto.TrimString([:]), textproto.TrimString([+1:])
var httpRange
if == "" || [0] == '-' {
return nil, errors.New("invalid range")
}
, := strconv.ParseInt(, 10, 64)
if < 0 || != nil {
return nil, errors.New("invalid range")
}
if > {
=
}
.start = -
.length = - .start
} else {
, := strconv.ParseInt(, 10, 64)
if != nil || < 0 {
return nil, errors.New("invalid range")
}
return nil, errNoOverlap
}
return , nil
}
type countingWriter int64
func ( *countingWriter) ( []byte) ( int, error) {
* += countingWriter(len())
return len(), nil
}
func ( []httpRange, string, int64) ( int64) {
var countingWriter
:= multipart.NewWriter(&)
for , := range {
.CreatePart(.mimeHeader(, ))
+= .length
}
.Close()
+= int64()
return
}
func ( []httpRange) ( int64) {
for , := range {
+= .length
}
return
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |