Example #1
0
func (ui *UIHandler) serveUploadHelper(rw http.ResponseWriter, req *http.Request) {
	if ui.root.Storage == nil {
		httputil.ServeJSONError(rw, httputil.ServerError("No BlobRoot configured"))
		return
	}

	mr, err := req.MultipartReader()
	if err != nil {
		httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error()))
		return
	}

	var got []*uploadHelperGotItem
	var modTime types.Time3339
	for {
		part, err := mr.NextPart()
		if err == io.EOF {
			break
		}
		if err != nil {
			httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error()))
			break
		}
		if part.FormName() == "modtime" {
			payload, err := ioutil.ReadAll(part)
			if err != nil {
				log.Printf("ui uploadhelper: unable to read part for modtime: %v", err)
				continue
			}
			modTime = types.ParseTime3339OrZero(string(payload))
			continue
		}
		fileName := part.FileName()
		if fileName == "" {
			continue
		}
		br, err := schema.WriteFileFromReaderWithModTime(ui.root.Storage, fileName, modTime.Time(), part)
		if err != nil {
			httputil.ServeJSONError(rw, httputil.ServerError("writing to blobserver: "+err.Error()))
			return
		}
		got = append(got, &uploadHelperGotItem{
			FileName: part.FileName(),
			ModTime:  modTime,
			FormName: part.FormName(),
			FileRef:  br,
		})
	}

	httputil.ReturnJSON(rw, &uploadHelperResponse{Got: got})
}
Example #2
0
func (sh *SetupHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if !auth.LocalhostAuthorized(req) {
		fmt.Fprintf(rw,
			"<html><body>Setup only allowed from localhost"+
				"<p><a href='/'>Back</a></p>"+
				"</body></html>\n")
		return
	}
	if req.Method == "POST" {
		err := req.ParseMultipartForm(10e6)
		if err != nil {
			httputil.ServerError(rw, req, err)
			return
		}
		if len(req.Form) > 0 {
			handleSetupChange(rw, req)
			return
		}
		if strings.Contains(req.URL.Path, "restartCamli") {
			err = osutil.RestartProcess()
			if err != nil {
				log.Fatal("Failed to restart: " + err.Error())
			}
		}
	}

	sendWizard(rw, req, false)
}
Example #3
0
func (ui *UIHandler) serveUploadHelper(rw http.ResponseWriter, req *http.Request) {
	if ui.root.Storage == nil {
		httputil.ServeJSONError(rw, httputil.ServerError("No BlobRoot configured"))
		return
	}

	mr, err := req.MultipartReader()
	if err != nil {
		httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error()))
		return
	}

	var got []*uploadHelperGotItem
	for {
		part, err := mr.NextPart()
		if err == io.EOF {
			break
		}
		if err != nil {
			httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error()))
			break
		}
		fileName := part.FileName()
		if fileName == "" {
			continue
		}
		br, err := schema.WriteFileFromReader(ui.root.Storage, fileName, part)
		if err != nil {
			httputil.ServeJSONError(rw, httputil.ServerError("writing to blobserver: "+err.Error()))
			return
		}
		got = append(got, &uploadHelperGotItem{
			FileName: part.FileName(),
			FormName: part.FormName(),
			FileRef:  br,
		})
	}

	httputil.ReturnJSON(rw, &uploadHelperResponse{Got: got})
}
Example #4
0
func setupHome(rw http.ResponseWriter, req *http.Request) {
	port := httputil.RequestTargetPort(req)
	localhostAddr, err := netutil.Localhost()
	if err != nil {
		httputil.ServerError(rw, req, err)
	}
	ourAddr := &net.TCPAddr{IP: localhostAddr, Port: port}
	rAddr, err := net.ResolveTCPAddr("tcp", req.RemoteAddr)
	if err != nil {
		fmt.Printf("camlistored: unable to resolve RemoteAddr %q: %v", req.RemoteAddr, err)
		return
	}
	uid, err := netutil.AddrPairUserid(rAddr, ourAddr)
	if err != nil {
		httputil.ServerError(rw, req, err)
	}

	fmt.Fprintf(rw, "Hello %q\n", req.RemoteAddr)
	fmt.Fprintf(rw, "<p>uid = %d\n", syscall.Getuid())
	fmt.Fprintf(rw, "<p>euid = %d\n", syscall.Geteuid())

	fmt.Fprintf(rw, "<p>http_local_uid(%q => %q) = %d (%v)\n", req.RemoteAddr, ourAddr, uid, err)
}
Example #5
0
func sendWizard(rw http.ResponseWriter, req *http.Request, hasChanged bool) {
	config, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	err = flattenPublish(config)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	funcMap := template.FuncMap{
		"printWizard":    printWizard,
		"inputIsGallery": func(inputName string) bool { return inputName == "gallery" },
	}

	body := `<form id="WizardForm" action="setup" method="post" enctype="multipart/form-data">`
	body += `{{range $k,$v := .}}{{printf "%v" $k}} <input type="text" size="30" name ="{{printf "%v" $k}}" value="{{printWizard $v}}" {{if inputIsGallery $k}}placeholder="/pics/,sha1-xxxx,pics.css"{{end}}><br />{{end}}`
	body += `<input type="submit" form="WizardForm" value="Save"></form>`

	if hasChanged {
		body += `<p> Configuration succesfully rewritten </p>`
	}

	tmpl, err := template.New("wizard").Funcs(funcMap).Parse(topWizard + body + bottomWizard)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}
	err = tmpl.Execute(rw, config)
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}
}
Example #6
0
func discoveryHelper(rw http.ResponseWriter, req *http.Request, dr *camtypes.Discovery) {
	rw.Header().Set("Content-Type", "text/javascript")
	if cb := req.FormValue("cb"); identOrDotPattern.MatchString(cb) {
		fmt.Fprintf(rw, "%s(", cb)
		defer rw.Write([]byte(");\n"))
	} else if v := req.FormValue("var"); identOrDotPattern.MatchString(v) {
		fmt.Fprintf(rw, "%s = ", v)
		defer rw.Write([]byte(";\n"))
	}
	bytes, err := json.MarshalIndent(dr, "", "  ")
	if err != nil {
		httputil.ServeJSONError(rw, httputil.ServerError("encoding discovery information: "+err.Error()))
		return
	}
	rw.Write(bytes)
}
Example #7
0
func handlePut(conn http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiver) {
	if w, ok := blobReceiver.(blobserver.ContextWrapper); ok {
		blobReceiver = w.WrapContext(req)
	}

	blobRef := blobref.FromPattern(kPutPattern, req.URL.Path)
	if blobRef == nil {
		httputil.BadRequestError(conn, "Malformed PUT URL.")
		return
	}

	if !blobRef.IsSupported() {
		httputil.BadRequestError(conn, "unsupported object hash function")
		return
	}

	_, err := blobReceiver.ReceiveBlob(blobRef, req.Body)
	if err != nil {
		httputil.ServerError(conn, err)
		return
	}

	fmt.Fprint(conn, "OK")
}
Example #8
0
func handleSetupChange(rw http.ResponseWriter, req *http.Request) {
	hilevelConf, err := jsonconfig.ReadFile(osutil.UserServerConfigPath())
	if err != nil {
		httputil.ServerError(rw, req, err)
		return
	}

	hasChanged := false
	var el interface{}
	publish := jsonconfig.Obj{}
	for k, v := range req.Form {
		if _, ok := hilevelConf[k]; !ok {
			if k != "gallery" && k != "blog" {
				continue
			}
		}

		switch k {
		case "https":
			b, err := strconv.ParseBool(v[0])
			if err != nil {
				httputil.ServerError(rw, req, fmt.Errorf("https field expects a boolean value"))
			}
			el = b
		case "replicateTo":
			// TODO(mpl): figure out why it is always seen as different from the conf
			el = []interface{}{}
			if len(v[0]) > 0 {
				els := []string{}
				vals := strings.Split(v[0], ",")
				els = append(els, vals...)
				el = els
			}
		// TODO(mpl): "handler,rootPermanode[,style]" for each published entity for now.
		// we will need something more readable later probably
		case "gallery", "blog":
			if len(v[0]) > 0 {
				pub := strings.Split(v[0], ",")
				if len(pub) < 2 || len(pub) > 3 {
					// no need to fail loudly for now as we'll probably change this format
					continue
				}
				handler := jsonconfig.Obj{}
				handler["template"] = k
				handler["rootPermanode"] = pub[1]
				if len(pub) > 2 {
					handler["style"] = pub[2]
				}
				publish[pub[0]] = handler
			}
			continue
		default:
			el = v[0]
		}
		if reflect.DeepEqual(hilevelConf[k], el) {
			continue
		}
		hasChanged = true
		hilevelConf[k] = el
	}
	// "publish" wasn't checked yet
	if !reflect.DeepEqual(hilevelConf["publish"], publish) {
		hilevelConf["publish"] = publish
		hasChanged = true
	}

	if hasChanged {
		err = rewriteConfig(&hilevelConf, osutil.UserServerConfigPath())
		if err != nil {
			httputil.ServerError(rw, req, err)
			return
		}
	}
	sendWizard(rw, req, hasChanged)
	return
}
Example #9
0
// serveBlobRef sends 'blobref' to 'conn' as directed by the Range header in 'req'
func serveBlobRef(conn http.ResponseWriter, req *http.Request,
	blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) {

	if w, ok := fetcher.(blobserver.ContextWrapper); ok {
		fetcher = w.WrapContext(req)
	}

	file, size, err := fetcher.FetchStreaming(blobRef)
	switch err {
	case nil:
		break
	case os.ErrNotExist:
		conn.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(conn, "Blob %q not found", blobRef)
		return
	default:
		httputil.ServerError(conn, req, err)
		return
	}

	defer file.Close()

	seeker, isSeeker := file.(io.Seeker)
	reqRange := httprange.FromRequest(req)
	if reqRange.SkipBytes() != 0 && isSeeker {
		// TODO: set the Range-specific response headers too,
		// acknowledging that we honored the content range
		// request.
		_, err = seeker.Seek(reqRange.SkipBytes(), 0)
		if err != nil {
			httputil.ServerError(conn, req, err)
			return
		}
	}

	var input io.Reader = file
	if reqRange.LimitBytes() != -1 {
		input = io.LimitReader(file, reqRange.LimitBytes())
	}

	remainBytes := size - reqRange.SkipBytes()
	if reqRange.LimitBytes() != -1 &&
		reqRange.LimitBytes() < remainBytes {
		remainBytes = reqRange.LimitBytes()
	}

	conn.Header().Set("Content-Type", "application/octet-stream")
	if reqRange.IsWholeFile() {
		conn.Header().Set("Content-Length", strconv.FormatInt(size, 10))
		// If it's small and all UTF-8, assume it's text and
		// just render it in the browser.  This is more for
		// demos/debuggability than anything else.  It isn't
		// part of the spec.
		if size <= 32<<10 {
			var buf bytes.Buffer
			_, err := io.Copy(&buf, input)
			if err != nil {
				httputil.ServerError(conn, req, err)
				return
			}
			if utf8.Valid(buf.Bytes()) {
				conn.Header().Set("Content-Type", "text/plain; charset=utf-8")
			}
			input = &buf
		}
	}

	if !reqRange.IsWholeFile() {
		conn.Header().Set("Content-Range",
			fmt.Sprintf("bytes %d-%d/%d", reqRange.SkipBytes(),
				reqRange.SkipBytes()+remainBytes,
				size))
		conn.WriteHeader(http.StatusPartialContent)
	}
	bytesCopied, err := io.Copy(conn, input)

	// If there's an error at this point, it's too late to tell the client,
	// as they've already been receiving bytes.  But they should be smart enough
	// to verify the digest doesn't match.  But we close the (chunked) response anyway,
	// to further signal errors.
	killConnection := func() {
		if hj, ok := conn.(http.Hijacker); ok {
			log.Printf("Force-closing TCP connection to signal error sending %q", blobRef)
			if closer, _, err := hj.Hijack(); err != nil {
				closer.Close()
			}
		}
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error sending file: %v, err=%v\n", blobRef, err)
		killConnection()
		return
	}

	if bytesCopied != remainBytes {
		fmt.Fprintf(os.Stderr, "Error sending file: %v, copied=%d, not %d\n", blobRef,
			bytesCopied, remainBytes)
		killConnection()
		return
	}
}
Example #10
0
func handleStat(conn http.ResponseWriter, req *http.Request, storage blobserver.BlobStatter) {
	if w, ok := storage.(blobserver.ContextWrapper); ok {
		storage = w.WrapContext(req)
	}

	toStat := make([]*blobref.BlobRef, 0)
	switch req.Method {
	case "POST":
		fallthrough
	case "GET":
		camliVersion := req.FormValue("camliversion")
		if camliVersion == "" {
			httputil.BadRequestError(conn, "No camliversion")
			return
		}
		n := 0
		for {
			n++
			key := fmt.Sprintf("blob%v", n)
			value := req.FormValue(key)
			if value == "" {
				n--
				break
			}
			if n > maxStatBlobs {
				httputil.BadRequestError(conn, "Too many stat blob checks")
				return
			}
			ref := blobref.Parse(value)
			if ref == nil {
				httputil.BadRequestError(conn, "Bogus blobref for key "+key)
				return
			}
			toStat = append(toStat, ref)
		}
	default:
		httputil.BadRequestError(conn, "Invalid method.")
		return

	}

	waitSeconds := 0
	if waitStr := req.FormValue("maxwaitsec"); waitStr != "" {
		waitSeconds, _ = strconv.Atoi(waitStr)
		switch {
		case waitSeconds < 0:
			waitSeconds = 0
		case waitSeconds > 30:
			// TODO: don't hard-code 30.  push this up into a blobserver interface
			// for getting the configuration of the server (ultimately a flag in
			// in the binary)
			waitSeconds = 30
		}
	}

	statRes := make([]map[string]interface{}, 0)
	if len(toStat) > 0 {
		blobch := make(chan blobref.SizedBlobRef)
		resultch := make(chan error, 1)
		go func() {
			err := storage.StatBlobs(blobch, toStat, time.Duration(waitSeconds)*time.Second)
			close(blobch)
			resultch <- err
		}()

		for sb := range blobch {
			ah := make(map[string]interface{})
			ah["blobRef"] = sb.BlobRef.String()
			ah["size"] = sb.Size
			statRes = append(statRes, ah)
		}

		err := <-resultch
		if err != nil {
			log.Printf("Stat error: %v", err)
			conn.WriteHeader(http.StatusInternalServerError)
			return
		}
	}

	configer, _ := storage.(blobserver.Configer)
	ret, err := commonUploadResponse(configer, req)
	if err != nil {
		httputil.ServerError(conn, req, err)
	}
	ret["canLongPoll"] = false
	if configer != nil {
		if conf := configer.Config(); conf != nil {
			ret["canLongPoll"] = conf.CanLongPoll
		}
	}
	ret["stat"] = statRes
	httputil.ReturnJSON(conn, ret)
}
Example #11
0
func handleMultiPartUpload(conn http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiveConfiger) {
	if w, ok := blobReceiver.(blobserver.ContextWrapper); ok {
		blobReceiver = wrapReceiveConfiger(w, req, blobReceiver)
	}

	if !(req.Method == "POST" && strings.Contains(req.URL.Path, "/camli/upload")) {
		log.Printf("Inconfigured handler upload handler")
		httputil.BadRequestError(conn, "Inconfigured handler.")
		return
	}

	receivedBlobs := make([]blobref.SizedBlobRef, 0, 10)

	multipart, err := req.MultipartReader()
	if multipart == nil {
		httputil.BadRequestError(conn, fmt.Sprintf(
			"Expected multipart/form-data POST request; %v", err))
		return
	}

	var errText string
	addError := func(s string) {
		log.Printf("Client error: %s", s)
		if errText == "" {
			errText = s
			return
		}
		errText = errText + "\n" + s
	}

	for {
		mimePart, err := multipart.NextPart()
		if err == io.EOF {
			break
		}
		if err != nil {
			addError(fmt.Sprintf("Error reading multipart section: %v", err))
			break
		}

		//POST-r60:
		//contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition"))
		//if err != nil {
		//	addError(err.String())
		//	break
		//}
		// r60:
		contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition"))
		if contentDisposition == "" || err != nil {
			addError("invalid Content-Disposition")
			break
		}

		if contentDisposition != "form-data" {
			addError(fmt.Sprintf("Expected Content-Disposition of \"form-data\"; got %q", contentDisposition))
			break
		}

		formName := params["name"]
		ref := blobref.Parse(formName)
		if ref == nil {
			addError(fmt.Sprintf("Ignoring form key %q", formName))
			continue
		}

		if oldAppEngineHappySpec {
			_, hasContentType := mimePart.Header["Content-Type"]
			if !hasContentType {
				addError(fmt.Sprintf("Expected Content-Type header for blobref %s; see spec", ref))
				continue
			}

			_, hasFileName := params["filename"]
			if !hasFileName {
				addError(fmt.Sprintf("Expected 'filename' Content-Disposition parameter for blobref %s; see spec", ref))
				continue
			}
		}

		blobGot, err := blobReceiver.ReceiveBlob(ref, mimePart)
		if err != nil {
			addError(fmt.Sprintf("Error receiving blob %v: %v\n", ref, err))
			break
		}
		log.Printf("Received blob %v\n", blobGot)
		receivedBlobs = append(receivedBlobs, blobGot)
	}

	ret, err := commonUploadResponse(blobReceiver, req)
	if err != nil {
		httputil.ServerError(conn, req, err)
	}

	received := make([]map[string]interface{}, 0)
	for _, got := range receivedBlobs {
		blob := make(map[string]interface{})
		blob["blobRef"] = got.BlobRef.String()
		blob["size"] = got.Size
		received = append(received, blob)
	}
	ret["received"] = received

	if req.Header.Get("X-Camlistore-Vivify") == "1" {
		for _, got := range receivedBlobs {
			err := vivify(blobReceiver, got)
			if err != nil {
				addError(fmt.Sprintf("Error vivifying blob %v: %v\n", got.BlobRef.String(), err))
			} else {
				conn.Header().Add("X-Camlistore-Vivified", got.BlobRef.String())
			}
		}
	}

	if errText != "" {
		ret["errorText"] = errText
	}

	httputil.ReturnJSON(conn, ret)
}
Example #12
0
// serveBlobRef sends 'blobref' to 'conn' as directed by the Range header in 'req'
func serveBlobRef(conn http.ResponseWriter, req *http.Request,
	blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) {

	if w, ok := fetcher.(blobserver.ContextWrapper); ok {
		fetcher = w.WrapContext(req)
	}

	file, size, err := fetcher.FetchStreaming(blobRef)
	switch err {
	case nil:
		break
	case os.ErrNotExist:
		conn.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(conn, "Blob %q not found", blobRef)
		return
	default:
		httputil.ServerError(conn, req, err)
		return
	}

	defer file.Close()

	seeker, isSeeker := file.(io.Seeker)
	reqRange := httprange.FromRequest(req)
	if reqRange.SkipBytes() != 0 && isSeeker {
		// TODO: set the Range-specific response headers too,
		// acknowledging that we honored the content range
		// request.
		_, err = seeker.Seek(reqRange.SkipBytes(), 0)
		if err != nil {
			httputil.ServerError(conn, req, err)
			return
		}
	}

	var input io.Reader = file
	if reqRange.LimitBytes() != -1 {
		input = io.LimitReader(file, reqRange.LimitBytes())
	}

	remainBytes := size - reqRange.SkipBytes()
	if reqRange.LimitBytes() != -1 &&
		reqRange.LimitBytes() < remainBytes {
		remainBytes = reqRange.LimitBytes()
	}

	// Assume this generic content type by default.  For better
	// demos we'll try to sniff and guess the "right" MIME type in
	// certain cases (no Range requests, etc) but this isn't part
	// of the Camli spec at all.  We just do it to ease demos.
	contentType := "application/octet-stream"
	if reqRange.IsWholeFile() {
		const peekSize = 1024
		bufReader := bufio.NewReaderSize(input, peekSize)
		header, _ := bufReader.Peek(peekSize)
		if len(header) >= 8 {
			switch {
			case utf8.Valid(header):
				contentType = "text/plain; charset=utf-8"
			case bytes.HasPrefix(header, []byte{0xff, 0xd8, 0xff, 0xe2}):
				contentType = "image/jpeg"
			case bytes.HasPrefix(header, []byte{0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa}):
				contentType = "image/png"
			}
		}
		input = bufReader

		conn.Header().Set("Content-Length", strconv.FormatInt(size, 10))
	}

	conn.Header().Set("Content-Type", contentType)
	if !reqRange.IsWholeFile() {
		conn.Header().Set("Content-Range",
			fmt.Sprintf("bytes %d-%d/%d", reqRange.SkipBytes(),
				reqRange.SkipBytes()+remainBytes,
				size))
		conn.WriteHeader(http.StatusPartialContent)
	}
	bytesCopied, err := io.Copy(conn, input)

	// If there's an error at this point, it's too late to tell the client,
	// as they've already been receiving bytes.  But they should be smart enough
	// to verify the digest doesn't match.  But we close the (chunked) response anyway,
	// to further signal errors.
	killConnection := func() {
		if hj, ok := conn.(http.Hijacker); ok {
			log.Printf("Force-closing TCP connection to signal error sending %q", blobRef)
			if closer, _, err := hj.Hijack(); err != nil {
				closer.Close()
			}
		}
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error sending file: %v, err=%v\n", blobRef, err)
		killConnection()
		return
	}

	if bytesCopied != remainBytes {
		fmt.Fprintf(os.Stderr, "Error sending file: %v, copied=%d, not %d\n", blobRef,
			bytesCopied, remainBytes)
		killConnection()
		return
	}
}