fs.go 23 KB
Newer Older
Russ Cox's avatar
Russ Cox committed
1 2 3 4 5 6 7 8 9
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// HTTP file system request handler

package http

import (
10
	"errors"
11 12
	"fmt"
	"io"
13
	"mime"
14 15
	"mime/multipart"
	"net/textproto"
16
	"net/url"
17
	"os"
18
	"path"
19
	"path/filepath"
20
	"sort"
21
	"strconv"
22
	"strings"
23
	"time"
Russ Cox's avatar
Russ Cox committed
24 25
)

26 27 28 29 30 31
// A Dir implements FileSystem using the native file system restricted to a
// specific directory tree.
//
// While the FileSystem.Open method takes '/'-separated paths, a Dir's string
// value is a filename on the native file system, not a URL, so it is separated
// by filepath.Separator, which isn't necessarily '/'.
32
//
33 34 35 36 37 38
// Note that Dir will allow access to files and directories starting with a
// period, which could expose sensitive directories like a .git directory or
// sensitive files like .htpasswd. To exclude files with a leading period,
// remove the files/directories from the server or create a custom FileSystem
// implementation.
//
39
// An empty Dir is treated as ".".
40 41
type Dir string

42 43 44 45 46 47 48 49 50 51
// mapDirOpenError maps the provided non-nil error from opening name
// to a possibly better non-nil error. In particular, it turns OS-specific errors
// about opening files in non-directories into os.ErrNotExist. See Issue 18984.
func mapDirOpenError(originalErr error, name string) error {
	if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
		return originalErr
	}

	parts := strings.Split(name, string(filepath.Separator))
	for i := range parts {
52 53 54
		if parts[i] == "" {
			continue
		}
55 56 57 58 59 60 61 62 63 64 65
		fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
		if err != nil {
			return originalErr
		}
		if !fi.IsDir() {
			return os.ErrNotExist
		}
	}
	return originalErr
}

66
func (d Dir) Open(name string) (File, error) {
67
	if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
68
		return nil, errors.New("http: invalid character in file path")
69
	}
70 71 72 73
	dir := string(d)
	if dir == "" {
		dir = "."
	}
74 75
	fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
	f, err := os.Open(fullName)
76
	if err != nil {
77
		return nil, mapDirOpenError(err, fullName)
78 79 80 81 82 83 84 85
	}
	return f, nil
}

// A FileSystem implements access to a collection of named files.
// The elements in a file path are separated by slash ('/', U+002F)
// characters, regardless of host operating system convention.
type FileSystem interface {
86
	Open(name string) (File, error)
87 88 89 90
}

// A File is returned by a FileSystem's Open method and can be
// served by the FileServer implementation.
91 92
//
// The methods should behave the same as those on an *os.File.
93
type File interface {
94 95
	io.Closer
	io.Reader
96
	io.Seeker
97
	Readdir(count int) ([]os.FileInfo, error)
98
	Stat() (os.FileInfo, error)
99 100 101
}

func dirList(w ResponseWriter, f File) {
102 103 104 105 106 107 108 109
	dirs, err := f.Readdir(-1)
	if err != nil {
		// TODO: log err.Error() to the Server.ErrorLog, once it's possible
		// for a handler to get at its Server via the ResponseWriter. See
		// Issue 12438.
		Error(w, "Error reading directory", StatusInternalServerError)
		return
	}
110
	sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
111

112
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
113
	fmt.Fprintf(w, "<pre>\n")
114 115 116 117
	for _, d := range dirs {
		name := d.Name()
		if d.IsDir() {
			name += "/"
Russ Cox's avatar
Russ Cox committed
118
		}
119 120 121 122
		// name may contain '?' or '#', which must be escaped to remain
		// part of the URL path, and not indicate the start of a query
		// string or fragment.
		url := url.URL{Path: name}
123
		fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
Russ Cox's avatar
Russ Cox committed
124
	}
125
	fmt.Fprintf(w, "</pre>\n")
Russ Cox's avatar
Russ Cox committed
126 127
}

Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
128
// ServeContent replies to the request using the content in the
129
// provided ReadSeeker. The main benefit of ServeContent over io.Copy
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
130
// is that it handles Range requests properly, sets the MIME type, and
131 132
// handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since,
// and If-Range requests.
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
133 134 135 136 137 138 139 140
//
// If the response's Content-Type header is not set, ServeContent
// first tries to deduce the type from name's file extension and,
// if that fails, falls back to reading the first block of the content
// and passing it to DetectContentType.
// The name is otherwise unused; in particular it can be empty and is
// never sent in the response.
//
141
// If modtime is not the zero time or Unix epoch, ServeContent
142
// includes it in a Last-Modified header in the response. If the
143 144
// request includes an If-Modified-Since header, ServeContent uses
// modtime to decide whether the content needs to be sent at all.
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
145 146 147 148
//
// The content's Seek method must work: ServeContent uses
// a seek to the end of the content to determine its size.
//
149 150
// If the caller has set w's ETag header formatted per RFC 7232, section 2.3,
// ServeContent uses it to handle requests using If-Match, If-None-Match, or If-Range.
151
//
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
152 153
// Note that *os.File implements the io.ReadSeeker interface.
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
154
	sizeFunc := func() (int64, error) {
155
		size, err := content.Seek(0, io.SeekEnd)
156 157 158
		if err != nil {
			return 0, errSeeker
		}
159
		_, err = content.Seek(0, io.SeekStart)
160 161 162 163
		if err != nil {
			return 0, errSeeker
		}
		return size, nil
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
164
	}
165
	serveContent(w, req, name, modtime, sizeFunc, content)
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
166 167
}

168 169 170 171 172 173
// errSeeker is returned by ServeContent's sizeFunc when the content
// doesn't seek properly. The underlying Seeker's error text isn't
// included in the sizeFunc reply so it's not sent over HTTP to end
// users.
var errSeeker = errors.New("seeker can't seek")

174 175 176 177
// errNoOverlap is returned by serveContent's parseRange if first-byte-pos of
// all of the byte-range-spec values is greater than the content size.
var errNoOverlap = errors.New("invalid range: failed to overlap")

Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
178 179 180
// if name is empty, filename is unknown. (used for mime type, before sniffing)
// if modtime.IsZero(), modtime is unknown.
// content must be seeked to the beginning of the file.
181 182
// The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
183 184
	setLastModified(w, modtime)
	done, rangeReq := checkPreconditions(w, r, modtime)
185 186 187
	if done {
		return
	}
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
188 189 190

	code := StatusOK

191 192 193 194 195
	// If Content-Type isn't set, use the file's extension to find it, but
	// if the Content-Type is unset explicitly, do not sniff the type.
	ctypes, haveType := w.Header()["Content-Type"]
	var ctype string
	if !haveType {
196
		ctype = mime.TypeByExtension(filepath.Ext(name))
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
197 198
		if ctype == "" {
			// read a chunk to decide between utf-8 text and binary
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
199
			var buf [sniffLen]byte
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
200
			n, _ := io.ReadFull(content, buf[:])
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
201
			ctype = DetectContentType(buf[:n])
202
			_, err := content.Seek(0, io.SeekStart) // rewind to output whole file
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
203 204 205 206 207 208
			if err != nil {
				Error(w, "seeker can't seek", StatusInternalServerError)
				return
			}
		}
		w.Header().Set("Content-Type", ctype)
209 210
	} else if len(ctypes) > 0 {
		ctype = ctypes[0]
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
211 212
	}

213 214 215 216 217 218
	size, err := sizeFunc()
	if err != nil {
		Error(w, err.Error(), StatusInternalServerError)
		return
	}

Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
219 220
	// handle Content-Range header.
	sendSize := size
221
	var sendContent io.Reader = content
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
222
	if size >= 0 {
223
		ranges, err := parseRange(rangeReq, size)
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
224
		if err != nil {
225 226 227
			if err == errNoOverlap {
				w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
			}
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
228 229 230
			Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
			return
		}
231
		if sumRangesSize(ranges) > size {
232
			// The total number of bytes in all the ranges
233
			// is larger than the size of the file by
234
			// itself, so this is probably an attack, or a
235
			// dumb client. Ignore the range request.
236 237
			ranges = nil
		}
238 239 240 241 242 243 244 245 246 247 248 249 250
		switch {
		case len(ranges) == 1:
			// RFC 2616, Section 14.16:
			// "When an HTTP message includes the content of a single
			// range (for example, a response to a request for a
			// single range, or to a request for a set of ranges
			// that overlap without any holes), this content is
			// transmitted with a Content-Range header, and a
			// Content-Length header showing the number of bytes
			// actually transferred.
			// ...
			// A response to a request for a single range MUST NOT
			// be sent using the multipart/byteranges media type."
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
251
			ra := ranges[0]
252
			if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
253 254 255 256 257
				Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
				return
			}
			sendSize = ra.length
			code = StatusPartialContent
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
			w.Header().Set("Content-Range", ra.contentRange(size))
		case len(ranges) > 1:
			sendSize = rangesMIMESize(ranges, ctype, size)
			code = StatusPartialContent

			pr, pw := io.Pipe()
			mw := multipart.NewWriter(pw)
			w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
			sendContent = pr
			defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
			go func() {
				for _, ra := range ranges {
					part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
					if err != nil {
						pw.CloseWithError(err)
						return
					}
275
					if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
276 277 278 279 280 281 282 283 284 285 286
						pw.CloseWithError(err)
						return
					}
					if _, err := io.CopyN(part, content, ra.length); err != nil {
						pw.CloseWithError(err)
						return
					}
				}
				mw.Close()
				pw.Close()
			}()
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
287 288 289 290 291 292 293 294 295 296 297
		}

		w.Header().Set("Accept-Ranges", "bytes")
		if w.Header().Get("Content-Encoding") == "" {
			w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
		}
	}

	w.WriteHeader(code)

	if r.Method != "HEAD" {
298
		io.CopyN(w, sendContent, sendSize)
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
299 300 301
	}
}

302 303 304 305 306 307 308 309 310 311 312
// scanETag determines if a syntactically valid ETag is present at s. If so,
// the ETag and remaining text after consuming ETag is returned. Otherwise,
// it returns "", "".
func scanETag(s string) (etag string, remain string) {
	s = textproto.TrimString(s)
	start := 0
	if strings.HasPrefix(s, "W/") {
		start = 2
	}
	if len(s[start:]) < 2 || s[start] != '"' {
		return "", ""
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
313
	}
314 315 316 317 318 319 320 321 322 323
	// ETag is either W/"text" or "text".
	// See RFC 7232 2.3.
	for i := start + 1; i < len(s); i++ {
		c := s[i]
		switch {
		// Character values allowed in ETags.
		case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
		case c == '"':
			return string(s[:i+1]), s[i+1:]
		default:
324
			return "", ""
325 326 327 328
		}
	}
	return "", ""
}
329

330 331 332 333
// etagStrongMatch reports whether a and b match using strong ETag comparison.
// Assumes a and b are valid ETags.
func etagStrongMatch(a, b string) bool {
	return a == b && a != "" && a[0] == '"'
Brad Fitzpatrick's avatar
Brad Fitzpatrick committed
334 335
}

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
// etagWeakMatch reports whether a and b match using weak ETag comparison.
// Assumes a and b are valid ETags.
func etagWeakMatch(a, b string) bool {
	return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
}

// condResult is the result of an HTTP request precondition check.
// See https://tools.ietf.org/html/rfc7232 section 3.
type condResult int

const (
	condNone condResult = iota
	condTrue
	condFalse
)

func checkIfMatch(w ResponseWriter, r *Request) condResult {
	im := r.Header.Get("If-Match")
	if im == "" {
		return condNone
	}
	for {
		im = textproto.TrimString(im)
		if len(im) == 0 {
			break
		}
		if im[0] == ',' {
			im = im[1:]
			continue
		}
		if im[0] == '*' {
			return condTrue
		}
		etag, remain := scanETag(im)
		if etag == "" {
			break
372
		}
373 374 375 376 377 378 379 380 381
		if etagStrongMatch(etag, w.Header().get("Etag")) {
			return condTrue
		}
		im = remain
	}

	return condFalse
}

382
func checkIfUnmodifiedSince(r *Request, modtime time.Time) condResult {
383 384 385 386 387 388 389 390 391
	ius := r.Header.Get("If-Unmodified-Since")
	if ius == "" || isZeroTime(modtime) {
		return condNone
	}
	if t, err := ParseTime(ius); err == nil {
		// The Date-Modified header truncates sub-second precision, so
		// use mtime < t+1s instead of mtime <= t to check for unmodified.
		if modtime.Before(t.Add(1 * time.Second)) {
			return condTrue
392
		}
393
		return condFalse
394
	}
395 396
	return condNone
}
397

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
func checkIfNoneMatch(w ResponseWriter, r *Request) condResult {
	inm := r.Header.get("If-None-Match")
	if inm == "" {
		return condNone
	}
	buf := inm
	for {
		buf = textproto.TrimString(buf)
		if len(buf) == 0 {
			break
		}
		if buf[0] == ',' {
			buf = buf[1:]
		}
		if buf[0] == '*' {
			return condFalse
		}
		etag, remain := scanETag(buf)
416
		if etag == "" {
417
			break
418
		}
419 420 421 422 423 424 425 426
		if etagWeakMatch(etag, w.Header().get("Etag")) {
			return condFalse
		}
		buf = remain
	}
	return condTrue
}

427
func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
	if r.Method != "GET" && r.Method != "HEAD" {
		return condNone
	}
	ims := r.Header.Get("If-Modified-Since")
	if ims == "" || isZeroTime(modtime) {
		return condNone
	}
	t, err := ParseTime(ims)
	if err != nil {
		return condNone
	}
	// The Date-Modified header truncates sub-second precision, so
	// use mtime < t+1s instead of mtime <= t to check for unmodified.
	if modtime.Before(t.Add(1 * time.Second)) {
		return condFalse
	}
	return condTrue
}

func checkIfRange(w ResponseWriter, r *Request, modtime time.Time) condResult {
448
	if r.Method != "GET" && r.Method != "HEAD" {
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
		return condNone
	}
	ir := r.Header.get("If-Range")
	if ir == "" {
		return condNone
	}
	etag, _ := scanETag(ir)
	if etag != "" {
		if etagStrongMatch(etag, w.Header().Get("Etag")) {
			return condTrue
		} else {
			return condFalse
		}
	}
	// The If-Range value is typically the ETag value, but it may also be
	// the modtime date. See golang.org/issue/8367.
	if modtime.IsZero() {
		return condFalse
	}
	t, err := ParseTime(ir)
	if err != nil {
		return condFalse
	}
	if t.Unix() == modtime.Unix() {
		return condTrue
	}
	return condFalse
}

var unixEpochTime = time.Unix(0, 0)

// isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
func isZeroTime(t time.Time) bool {
	return t.IsZero() || t.Equal(unixEpochTime)
}

func setLastModified(w ResponseWriter, modtime time.Time) {
	if !isZeroTime(modtime) {
		w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
	}
}
490

491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
func writeNotModified(w ResponseWriter) {
	// RFC 7232 section 4.1:
	// a sender SHOULD NOT generate representation metadata other than the
	// above listed fields unless said metadata exists for the purpose of
	// guiding cache updates (e.g., Last-Modified might be useful if the
	// response does not have an ETag field).
	h := w.Header()
	delete(h, "Content-Type")
	delete(h, "Content-Length")
	if h.Get("Etag") != "" {
		delete(h, "Last-Modified")
	}
	w.WriteHeader(StatusNotModified)
}

// checkPreconditions evaluates request preconditions and reports whether a precondition
// resulted in sending StatusNotModified or StatusPreconditionFailed.
func checkPreconditions(w ResponseWriter, r *Request, modtime time.Time) (done bool, rangeHeader string) {
	// This function carefully follows RFC 7232 section 6.
	ch := checkIfMatch(w, r)
	if ch == condNone {
512
		ch = checkIfUnmodifiedSince(r, modtime)
513 514 515 516 517 518 519 520 521 522 523 524 525
	}
	if ch == condFalse {
		w.WriteHeader(StatusPreconditionFailed)
		return true, ""
	}
	switch checkIfNoneMatch(w, r) {
	case condFalse:
		if r.Method == "GET" || r.Method == "HEAD" {
			writeNotModified(w)
			return true, ""
		} else {
			w.WriteHeader(StatusPreconditionFailed)
			return true, ""
526
		}
527
	case condNone:
528
		if checkIfModifiedSince(r, modtime) == condFalse {
529 530 531 532
			writeNotModified(w)
			return true, ""
		}
	}
533

534 535 536 537
	rangeHeader = r.Header.get("Range")
	if rangeHeader != "" {
		if checkIfRange(w, r, modtime) == condFalse {
			rangeHeader = ""
538 539
		}
	}
540
	return false, rangeHeader
541 542
}

543 544
// name is '/'-separated, not filepath.Separator.
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
545
	const indexPage = "/index.html"
Russ Cox's avatar
Russ Cox committed
546

Russ Cox's avatar
Russ Cox committed
547
	// redirect .../index.html to .../
548 549
	// can't use Redirect() because that would make the path absolute,
	// which would be a problem running under StripPrefix
Russ Cox's avatar
Russ Cox committed
550
	if strings.HasSuffix(r.URL.Path, indexPage) {
551
		localRedirect(w, r, "./")
552
		return
Russ Cox's avatar
Russ Cox committed
553 554
	}

555
	f, err := fs.Open(name)
Russ Cox's avatar
Russ Cox committed
556
	if err != nil {
557 558
		msg, code := toHTTPError(err)
		Error(w, msg, code)
559
		return
Russ Cox's avatar
Russ Cox committed
560
	}
561
	defer f.Close()
Russ Cox's avatar
Russ Cox committed
562

563 564
	d, err := f.Stat()
	if err != nil {
565 566
		msg, code := toHTTPError(err)
		Error(w, msg, code)
567
		return
Russ Cox's avatar
Russ Cox committed
568 569 570 571
	}

	if redirect {
		// redirect to canonical path: / at end of directory url
572
		// r.URL.Path always begins with /
573
		url := r.URL.Path
574
		if d.IsDir() {
Russ Cox's avatar
Russ Cox committed
575
			if url[len(url)-1] != '/' {
576
				localRedirect(w, r, path.Base(url)+"/")
577
				return
Russ Cox's avatar
Russ Cox committed
578 579 580
			}
		} else {
			if url[len(url)-1] == '/' {
581
				localRedirect(w, r, "../"+path.Base(url))
582
				return
Russ Cox's avatar
Russ Cox committed
583 584 585 586
			}
		}
	}

587 588 589 590 591 592 593 594 595
	// redirect if the directory name doesn't end in a slash
	if d.IsDir() {
		url := r.URL.Path
		if url[len(url)-1] != '/' {
			localRedirect(w, r, path.Base(url)+"/")
			return
		}
	}

Russ Cox's avatar
Russ Cox committed
596
	// use contents of index.html for directory, if present
597
	if d.IsDir() {
598
		index := strings.TrimSuffix(name, "/") + indexPage
599
		ff, err := fs.Open(index)
Russ Cox's avatar
Russ Cox committed
600
		if err == nil {
601 602
			defer ff.Close()
			dd, err := ff.Stat()
Russ Cox's avatar
Russ Cox committed
603
			if err == nil {
604 605 606
				name = index
				d = dd
				f = ff
Russ Cox's avatar
Russ Cox committed
607 608 609 610
			}
		}
	}

611
	// Still a directory? (we didn't find an index.html file)
612
	if d.IsDir() {
613
		if checkIfModifiedSince(r, d.ModTime()) == condFalse {
614
			writeNotModified(w)
615 616
			return
		}
617
		w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
618
		dirList(w, f)
619
		return
Russ Cox's avatar
Russ Cox committed
620 621
	}

622
	// serveContent will check modification time
623 624
	sizeFunc := func() (int64, error) { return d.Size(), nil }
	serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
Russ Cox's avatar
Russ Cox committed
625 626
}

627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
// toHTTPError returns a non-specific HTTP error message and status code
// for a given non-nil error value. It's important that toHTTPError does not
// actually return err.Error(), since msg and httpStatus are returned to users,
// and historically Go's ServeContent always returned just "404 Not Found" for
// all errors. We don't want to start leaking information in error messages.
func toHTTPError(err error) (msg string, httpStatus int) {
	if os.IsNotExist(err) {
		return "404 page not found", StatusNotFound
	}
	if os.IsPermission(err) {
		return "403 Forbidden", StatusForbidden
	}
	// Default:
	return "500 Internal Server Error", StatusInternalServerError
}

643 644 645 646 647 648 649 650 651 652
// localRedirect gives a Moved Permanently response.
// It does not convert relative paths to absolute paths like Redirect does.
func localRedirect(w ResponseWriter, r *Request, newPath string) {
	if q := r.URL.RawQuery; q != "" {
		newPath += "?" + q
	}
	w.Header().Set("Location", newPath)
	w.WriteHeader(StatusMovedPermanently)
}

653 654 655
// ServeFile replies to the request with the contents of the named
// file or directory.
//
656
// If the provided file or directory name is a relative path, it is
657 658 659 660 661
// interpreted relative to the current directory and may ascend to parent
// directories. If the provided name is constructed from user input, it
// should be sanitized before calling ServeFile. As a precaution, ServeFile
// will reject requests where r.URL.Path contains a ".." path element.
//
662 663 664 665
// As a special case, ServeFile redirects any request where r.URL.Path
// ends in "/index.html" to the same path, without the final
// "index.html". To avoid such redirects either modify the path or
// use ServeContent.
666
func ServeFile(w ResponseWriter, r *Request, name string) {
667 668 669 670 671 672 673 674 675
	if containsDotDot(r.URL.Path) {
		// Too many programs use r.URL.Path to construct the argument to
		// serveFile. Reject the request under the assumption that happened
		// here and ".." may not be wanted.
		// Note that name might not contain "..", for example if code (still
		// incorrectly) used filepath.Join(myDir, r.URL.Path).
		Error(w, "invalid URL path", StatusBadRequest)
		return
	}
676 677
	dir, file := filepath.Split(name)
	serveFile(w, r, Dir(dir), file, false)
Russ Cox's avatar
Russ Cox committed
678 679
}

680 681 682 683 684 685 686 687 688 689 690 691 692 693
func containsDotDot(v string) bool {
	if !strings.Contains(v, "..") {
		return false
	}
	for _, ent := range strings.FieldsFunc(v, isSlashRune) {
		if ent == ".." {
			return true
		}
	}
	return false
}

func isSlashRune(r rune) bool { return r == '/' || r == '\\' }

Russ Cox's avatar
Russ Cox committed
694
type fileHandler struct {
695
	root FileSystem
Russ Cox's avatar
Russ Cox committed
696 697 698 699
}

// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
700 701 702 703 704
//
// To use the operating system's file system implementation,
// use http.Dir:
//
//     http.Handle("/", http.FileServer(http.Dir("/tmp")))
705 706 707 708
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
709 710
func FileServer(root FileSystem) Handler {
	return &fileHandler{root}
711
}
Russ Cox's avatar
Russ Cox committed
712

713
func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
714 715 716 717 718 719
	upath := r.URL.Path
	if !strings.HasPrefix(upath, "/") {
		upath = "/" + upath
		r.URL.Path = upath
	}
	serveFile(w, r, f.root, path.Clean(upath), true)
Russ Cox's avatar
Russ Cox committed
720
}
721 722 723 724 725 726

// httpRange specifies the byte range to be sent to the client.
type httpRange struct {
	start, length int64
}

727 728 729 730 731 732 733 734 735 736 737
func (r httpRange) contentRange(size int64) string {
	return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
}

func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
	return textproto.MIMEHeader{
		"Content-Range": {r.contentRange(size)},
		"Content-Type":  {contentType},
	}
}

738
// parseRange parses a Range header string as per RFC 2616.
739
// errNoOverlap is returned if none of the ranges overlap.
740
func parseRange(s string, size int64) ([]httpRange, error) {
741 742 743 744 745
	if s == "" {
		return nil, nil // header not present
	}
	const b = "bytes="
	if !strings.HasPrefix(s, b) {
746
		return nil, errors.New("invalid range")
747 748
	}
	var ranges []httpRange
749
	noOverlap := false
750
	for _, ra := range strings.Split(s[len(b):], ",") {
751 752 753 754
		ra = strings.TrimSpace(ra)
		if ra == "" {
			continue
		}
755
		i := strings.Index(ra, "-")
756
		if i < 0 {
757
			return nil, errors.New("invalid range")
758
		}
759
		start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
760 761 762 763
		var r httpRange
		if start == "" {
			// If no start is specified, end specifies the
			// range start relative to the end of the file.
Russ Cox's avatar
Russ Cox committed
764
			i, err := strconv.ParseInt(end, 10, 64)
765
			if err != nil {
766
				return nil, errors.New("invalid range")
767 768 769 770 771 772 773
			}
			if i > size {
				i = size
			}
			r.start = size - i
			r.length = size - r.start
		} else {
Russ Cox's avatar
Russ Cox committed
774
			i, err := strconv.ParseInt(start, 10, 64)
775
			if err != nil || i < 0 {
776
				return nil, errors.New("invalid range")
777
			}
778 779 780 781 782 783
			if i >= size {
				// If the range begins after the size of the content,
				// then it does not overlap.
				noOverlap = true
				continue
			}
784 785 786 787 788
			r.start = i
			if end == "" {
				// If no end is specified, range extends to end of the file.
				r.length = size - r.start
			} else {
Russ Cox's avatar
Russ Cox committed
789
				i, err := strconv.ParseInt(end, 10, 64)
790
				if err != nil || r.start > i {
791
					return nil, errors.New("invalid range")
792 793 794 795 796 797 798 799 800
				}
				if i >= size {
					i = size - 1
				}
				r.length = i - r.start + 1
			}
		}
		ranges = append(ranges, r)
	}
801 802 803 804
	if noOverlap && len(ranges) == 0 {
		// The specified ranges did not overlap with the content.
		return nil, errNoOverlap
	}
805 806
	return ranges, nil
}
807 808 809 810 811 812 813 814 815

// countingWriter counts how many bytes have been written to it.
type countingWriter int64

func (w *countingWriter) Write(p []byte) (n int, err error) {
	*w += countingWriter(len(p))
	return len(p), nil
}

816
// rangesMIMESize returns the number of bytes it takes to encode the
817 818 819 820 821 822 823 824 825 826 827 828
// provided ranges as a multipart response.
func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
	var w countingWriter
	mw := multipart.NewWriter(&w)
	for _, ra := range ranges {
		mw.CreatePart(ra.mimeHeader(contentType, contentSize))
		encSize += ra.length
	}
	mw.Close()
	encSize += int64(w)
	return
}
829 830 831 832 833 834 835

func sumRangesSize(ranges []httpRange) (size int64) {
	for _, ra := range ranges {
		size += ra.length
	}
	return
}