Beispiel #1
0
func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	suffix := httputil.PathSuffix(req)

	handlers := getHandler
	switch {
	case httputil.IsGet(req):
		// use default from above
	case req.Method == "POST":
		handlers = postHandler
	default:
		handlers = nil
	}
	fn := handlers[strings.TrimPrefix(suffix, "camli/search/")]
	if fn != nil {
		fn(sh, rw, req)
		return
	}

	// TODO: discovery for the endpoints & better error message with link to discovery info
	ret := camtypes.SearchErrorResponse{
		Error:     "Unsupported search path or method",
		ErrorType: "input",
	}
	httputil.ReturnJSON(rw, &ret)
}
Beispiel #2
0
func wantsClosure(req *http.Request) bool {
	if httputil.IsGet(req) {
		suffix := httputil.PathSuffix(req)
		return closurePattern.MatchString(suffix)
	}
	return false
}
Beispiel #3
0
func getSuffixMatches(req *http.Request, pattern *regexp.Regexp) bool {
	if httputil.IsGet(req) {
		suffix := httputil.PathSuffix(req)
		return pattern.MatchString(suffix)
	}
	return false
}
Beispiel #4
0
func wantsPermanode(req *http.Request) bool {
	if httputil.IsGet(req) && blob.ValidRefString(req.FormValue("p")) {
		// The new UI is handled by index.html.
		if req.FormValue("newui") != "1" {
			return true
		}
	}
	return false
}
Beispiel #5
0
// fromHTTP panics with an httputil value on failure
func (r *DescribeRequest) fromHTTP(req *http.Request) {
	switch {
	case httputil.IsGet(req):
		r.fromHTTPGet(req)
	case req.Method == "POST":
		r.fromHTTPPost(req)
	default:
		panic("Unsupported method")
	}
}
Beispiel #6
0
func (o *ownerAuth) SendUnauthorized(rw http.ResponseWriter, req *http.Request) bool {
	if !httputil.IsGet(req) {
		return false
	}
	c := appengine.NewContext(req)
	loginURL, err := user.LoginURL(c, req.URL.String())
	if err != nil {
		c.Errorf("Fetching LoginURL: %v", err)
		return false
	}
	http.Redirect(rw, req, loginURL, http.StatusFound)
	return true
}
Beispiel #7
0
func (hh *HelpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	suffix := httputil.PathSuffix(req)
	if !httputil.IsGet(req) {
		http.Error(rw, "Illegal help method.", http.StatusMethodNotAllowed)
		return
	}
	switch suffix {
	case "":
		hh.serveHelpHTML(rw, req)
	default:
		http.Error(rw, "Illegal help path.", http.StatusNotFound)
	}
}
Beispiel #8
0
func (sh *StatusHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	suffix := httputil.PathSuffix(req)
	if !httputil.IsGet(req) {
		http.Error(rw, "Illegal status method.", http.StatusMethodNotAllowed)
		return
	}
	switch suffix {
	case "status.json":
		sh.serveStatusJSON(rw, req)
	case "":
		sh.serveStatusHTML(rw, req)
	default:
		http.Error(rw, "Illegal status path.", 404)
	}
}
Beispiel #9
0
func (hh *HelpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	suffix := httputil.PathSuffix(req)
	if !httputil.IsGet(req) {
		http.Error(rw, "Illegal help method.", http.StatusMethodNotAllowed)
		return
	}
	switch suffix {
	case "":
		if clientConfig := req.FormValue("clientConfig"); clientConfig != "" {
			if clientConfigOnly, err := strconv.ParseBool(clientConfig); err == nil && clientConfigOnly {
				httputil.ReturnJSON(rw, hh.clientConfig)
				return
			}
		}
		hh.serveHelpHTML(rw, req)
	default:
		http.Error(rw, "Illegal help path.", http.StatusNotFound)
	}
}
Beispiel #10
0
// serveRef gets the file at ref from fetcher and serves its contents.
// It is used by Service as a one time handler to serve to the thumbnail child process on localhost.
func serveRef(rw http.ResponseWriter, req *http.Request, ref blob.Ref, fetcher blob.Fetcher) {

	if !httputil.IsGet(req) {
		http.Error(rw, "Invalid download method.", 400)
		return
	}

	if !httputil.IsLocalhost(req) {
		http.Error(rw, "Forbidden.", 403)
		return
	}

	parts := strings.Split(req.URL.Path, "/")
	if len(parts) < 2 {
		http.Error(rw, "Malformed GET URL.", 400)
		return
	}

	blobRef, ok := blob.Parse(parts[1])
	if !ok {
		http.Error(rw, "Malformed GET URL.", 400)
		return
	}

	// only serves its ref
	if blobRef != ref {
		log.Printf("videothumbnail: access to %v forbidden; wrong blobref for handler", blobRef)
		http.Error(rw, "Forbidden.", 403)
		return
	}

	rw.Header().Set("Content-Type", "application/octet-stream")

	fr, err := schema.NewFileReader(fetcher, ref)
	if err != nil {
		httputil.ServeError(rw, req, err)
		return
	}
	defer fr.Close()

	http.ServeContent(rw, req, "", time.Now(), fr)
}
Beispiel #11
0
func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	ret := jsonMap()
	suffix := httputil.PathSuffix(req)

	if httputil.IsGet(req) {
		switch suffix {
		case "camli/search/recent":
			sh.serveRecentPermanodes(rw, req)
			return
		case "camli/search/permanodeattr":
			sh.servePermanodesWithAttr(rw, req)
			return
		case "camli/search/describe":
			sh.serveDescribe(rw, req)
			return
		case "camli/search/claims":
			sh.serveClaims(rw, req)
			return
		case "camli/search/files":
			sh.serveFiles(rw, req)
			return
		case "camli/search/signerattrvalue":
			sh.serveSignerAttrValue(rw, req)
			return
		case "camli/search/signerpaths":
			sh.serveSignerPaths(rw, req)
			return
		case "camli/search/edgesto":
			sh.serveEdgesTo(rw, req)
			return
		}
	}

	// TODO: discovery for the endpoints & better error message with link to discovery info
	ret["error"] = "Unsupported search path or method"
	ret["errorType"] = "input"
	httputil.ReturnJSON(rw, ret)
}
Beispiel #12
0
func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	ret := jsonMap()
	suffix := httputil.PathSuffix(req)

	if httputil.IsGet(req) {
		fn := getHandler[strings.TrimPrefix(suffix, "camli/search/")]
		if fn != nil {
			fn(sh, rw, req)
			return
		}
	}
	if req.Method == "POST" {
		switch suffix {
		case "camli/search/query":
			sh.serveQuery(rw, req)
			return
		}
	}

	// TODO: discovery for the endpoints & better error message with link to discovery info
	ret["error"] = "Unsupported search path or method"
	ret["errorType"] = "input"
	httputil.ReturnJSON(rw, ret)
}
Beispiel #13
0
func (ih *ImageHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, file blob.Ref) {
	if !httputil.IsGet(req) {
		http.Error(rw, "Invalid method", 400)
		return
	}
	mw, mh := ih.MaxWidth, ih.MaxHeight
	if mw == 0 || mh == 0 || mw > search.MaxImageSize || mh > search.MaxImageSize {
		http.Error(rw, "bogus dimensions", 400)
		return
	}

	key := cacheKey(file.String(), mw, mh)
	etag := blob.SHA1FromString(key).String()[5:]
	inm := req.Header.Get("If-None-Match")
	if inm != "" {
		if strings.Trim(inm, `"`) == etag {
			rw.WriteHeader(http.StatusNotModified)
			return
		}
	} else {
		if !disableThumbCache && req.Header.Get("If-Modified-Since") != "" {
			rw.WriteHeader(http.StatusNotModified)
			return
		}
	}

	var imageData []byte
	format := ""
	cacheHit := false
	if ih.thumbMeta != nil && !disableThumbCache {
		var buf bytes.Buffer
		format = ih.scaledCached(&buf, file)
		if format != "" {
			cacheHit = true
			imageData = buf.Bytes()
		}
	}

	if !cacheHit {
		imi, err := singleResize.Do(key, func() (interface{}, error) {
			return ih.scaleImage(file)
		})
		if err != nil {
			http.Error(rw, err.Error(), 500)
			return
		}
		im := imi.(*formatAndImage)
		imageData = im.image
		if ih.thumbMeta != nil {
			err := ih.cacheScaled(bytes.NewReader(imageData), key)
			if err != nil {
				log.Printf("image resize: %v", err)
			}
		}
	}

	h := rw.Header()
	if !disableThumbCache {
		h.Set("Expires", time.Now().Add(oneYear).Format(http.TimeFormat))
		h.Set("Last-Modified", time.Now().Format(http.TimeFormat))
		h.Set("Etag", strconv.Quote(etag))
	}
	h.Set("Content-Type", imageContentTypeOfFormat(format))
	size := len(imageData)
	h.Set("Content-Length", fmt.Sprint(size))
	imageBytesServedVar.Add(int64(size))

	if req.Method == "GET" {
		n, err := rw.Write(imageData)
		if err != nil {
			if strings.Contains(err.Error(), "broken pipe") {
				// boring.
				return
			}
			// TODO: vlog this:
			log.Printf("error serving thumbnail of file schema %s: %v", file, err)
			return
		}
		if n != size {
			log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d",
				file, n, size)
			return
		}
	}
}
Beispiel #14
0
// Unauthenticated user.  Be paranoid.
func handleGetViaSharing(conn http.ResponseWriter, req *http.Request,
	blobRef blob.Ref, fetcher blob.StreamingFetcher) error {
	if !httputil.IsGet(req) {
		return &shareError{code: invalidMethod, response: badRequest, message: "Invalid method"}
	}

	viaPathOkay := false
	startTime := time.Now()
	defer func() {
		if !viaPathOkay {
			// Insert a delay, to hide timing attacks probing
			// for the existence of blobs.
			sleep := fetchFailureDelay - (time.Now().Sub(startTime))
			time.Sleep(sleep)
		}
	}()
	viaBlobs := make([]blob.Ref, 0)
	if via := req.FormValue("via"); via != "" {
		for _, vs := range strings.Split(via, ",") {
			if br, ok := blob.Parse(vs); ok {
				viaBlobs = append(viaBlobs, br)
			} else {
				return &shareError{code: invalidVia, response: badRequest, message: "Malformed blobref in via param"}
			}
		}
	}

	fetchChain := make([]blob.Ref, 0)
	fetchChain = append(fetchChain, viaBlobs...)
	fetchChain = append(fetchChain, blobRef)
	isTransitive := false
	for i, br := range fetchChain {
		switch i {
		case 0:
			file, size, err := fetcher.FetchStreaming(br)
			if err != nil {
				return unauthorized(shareFetchFailed, "Fetch chain 0 of %s failed: %v", br, err)
			}
			defer file.Close()
			if size > schema.MaxSchemaBlobSize {
				return unauthorized(shareBlobTooLarge, "Fetch chain 0 of %s too large", br)
			}
			blob, err := schema.BlobFromReader(br, file)
			if err != nil {
				return unauthorized(shareReadFailed, "Can't create a blob from %v: %v", br, err)
			}
			share, ok := blob.AsShare()
			if !ok {
				return unauthorized(shareBlobInvalid, "Fetch chain 0 of %s wasn't a valid Share", br)
			}
			if share.IsExpired() {
				return unauthorized(shareExpired, "Share is expired")
			}
			if len(fetchChain) > 1 && fetchChain[1].String() != share.Target().String() {
				return unauthorized(shareTargetInvalid,
					"Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q",
					br, fetchChain[1], share.Target())
			}
			isTransitive = share.IsTransitive()
			if len(fetchChain) > 2 && !isTransitive {
				return unauthorized(shareNotTransitive, "Share is not transitive")
			}
		case len(fetchChain) - 1:
			// Last one is fine (as long as its path up to here has been proven, and it's
			// not the first thing in the chain)
			continue
		default:
			file, _, err := fetcher.FetchStreaming(br)
			if err != nil {
				return unauthorized(viaChainFetchFailed, "Fetch chain %d of %s failed: %v", i, br, err)
			}
			defer file.Close()
			lr := io.LimitReader(file, schema.MaxSchemaBlobSize)
			slurpBytes, err := ioutil.ReadAll(lr)
			if err != nil {
				return unauthorized(viaChainReadFailed,
					"Fetch chain %d of %s failed in slurp: %v", i, br, err)
			}
			saught := fetchChain[i+1].String()
			if bytes.Index(slurpBytes, []byte(saught)) == -1 {
				return unauthorized(viaChainInvalidLink,
					"Fetch chain %d of %s failed; no reference to %s", i, br, saught)
			}
		}
	}

	if assemble, _ := strconv.ParseBool(req.FormValue("assemble")); assemble {
		if !isTransitive {
			return unauthorized(assembleNonTransitive, "Cannot assemble non-transitive share")
		}
		dh := &DownloadHandler{
			Fetcher: fetcher,
			// TODO(aa): It would be nice to specify a local cache here, as the UI handler does.
		}
		dh.ServeHTTP(conn, req, blobRef)
	} else {
		gethandler.ServeBlobRef(conn, req, blobRef, fetcher)
	}
	viaPathOkay = true
	return nil
}
Beispiel #15
0
// ServeHTTP streams a zip archive of all the files "under"
// zh.root. That is, all the files pointed by file permanodes,
// which are directly members of zh.root or recursively down
// directory permanodes and permanodes members.
// To build the fullpath of a file in a collection, it uses
// the collection title if present, its blobRef otherwise, as
// a directory name.
func (zh *zipHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	// TODO: use http.ServeContent, so Range requests work and downloads can be resumed.
	// Will require calculating the zip length once first (ideally as cheaply as possible,
	// with dummy counting writer and dummy all-zero-byte-files of a fixed size),
	// and then making a dummy ReadSeeker for ServeContent that can seek to the end,
	// and then seek back to the beginning, but then seeks forward make it remember
	// to skip that many bytes from the archive/zip writer when answering Reads.
	if !httputil.IsGet(req) {
		http.Error(rw, "Invalid method", http.StatusMethodNotAllowed)
		return
	}
	bf, err := zh.blobList("/", zh.root)
	if err != nil {
		log.Printf("Could not serve zip for %v: %v", zh.root, err)
		http.Error(rw, "Server error", http.StatusInternalServerError)
		return
	}
	blobFiles := renameDuplicates(bf)

	// TODO(mpl): streaming directly won't work on appengine if the size goes
	// over 32 MB. Deal with that.
	h := rw.Header()
	h.Set("Content-Type", "application/zip")
	filename := zh.filename
	if filename == "" {
		filename = "download.zip"
	}
	h.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename}))
	zw := zip.NewWriter(rw)
	etag := sha1.New()
	for _, file := range blobFiles {
		etag.Write([]byte(file.blobRef.String()))
	}
	h.Set("Etag", fmt.Sprintf(`"%x"`, etag.Sum(nil)))

	for _, file := range blobFiles {
		fr, err := schema.NewFileReader(zh.storageSeekFetcher(), file.blobRef)
		if err != nil {
			log.Printf("Can not add %v in zip, not a file: %v", file.blobRef, err)
			http.Error(rw, "Server error", http.StatusInternalServerError)
			return
		}
		f, err := zw.CreateHeader(
			&zip.FileHeader{
				Name:   file.path,
				Method: zip.Store,
			})
		if err != nil {
			log.Printf("Could not create %q in zip: %v", file.path, err)
			http.Error(rw, "Server error", http.StatusInternalServerError)
			return
		}
		_, err = io.Copy(f, fr)
		fr.Close()
		if err != nil {
			log.Printf("Could not zip %q: %v", file.path, err)
			return
		}
	}
	err = zw.Close()
	if err != nil {
		log.Printf("Could not close zipwriter: %v", err)
		return
	}
}
Beispiel #16
0
func wantsFileTreePage(req *http.Request) bool {
	return httputil.IsGet(req) && blob.ValidRefString(req.FormValue("d"))
}
Beispiel #17
0
func wantsBlobInfo(req *http.Request) bool {
	return httputil.IsGet(req) && blob.ValidRefString(req.FormValue("b"))
}
Beispiel #18
0
func wantsPermanode(req *http.Request) bool {
	return httputil.IsGet(req) && blob.ValidRefString(req.FormValue("p"))
}
Beispiel #19
0
func wantsDiscovery(req *http.Request) bool {
	return httputil.IsGet(req) &&
		(req.Header.Get("Accept") == "text/x-camli-configuration" ||
			camliMode(req) == "config")
}
Beispiel #20
0
func (ih *ImageHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, file blob.Ref) {
	if !httputil.IsGet(req) {
		http.Error(rw, "Invalid method", 400)
		return
	}
	mw, mh := ih.MaxWidth, ih.MaxHeight
	if mw == 0 || mh == 0 || mw > search.MaxImageSize || mh > search.MaxImageSize {
		http.Error(rw, "bogus dimensions", 400)
		return
	}
	if req.Header.Get("If-Modified-Since") != "" {
		// Immutable, so any copy's a good copy.
		rw.WriteHeader(http.StatusNotModified)
		return
	}

	var buf bytes.Buffer
	var err error
	format := ""
	cacheHit := false
	if ih.sc != nil {
		format, err = ih.scaledCached(&buf, file)
		if err != nil {
			log.Printf("image resize: %v", err)
		} else {
			cacheHit = true
		}
	}

	if !cacheHit {
		format, err = ih.scaleImage(&buf, file)
		if err != nil {
			http.Error(rw, err.Error(), 500)
			return
		}
		if ih.sc != nil {
			name := cacheKey(file.String(), mw, mh)
			bufcopy := buf.Bytes()
			err = ih.cacheScaled(bytes.NewBuffer(bufcopy), name)
			if err != nil {
				log.Printf("image resize: %v", err)
			}
		}
	}

	h := rw.Header()
	h.Set("Expires", time.Now().Add(oneYear).Format(http.TimeFormat))
	h.Set("Last-Modified", time.Now().Format(http.TimeFormat))
	h.Set("Content-Type", imageContentTypeOfFormat(format))
	size := buf.Len()
	h.Set("Content-Length", fmt.Sprintf("%d", size))

	if req.Method == "GET" {
		n, err := io.Copy(rw, &buf)
		if err != nil {
			log.Printf("error serving thumbnail of file schema %s: %v", file, err)
			return
		}
		if n != int64(size) {
			log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d",
				file, n, size)
			return
		}
	}
}