2011-06-17 03:45:47 +00:00
|
|
|
/*
|
|
|
|
Copyright 2011 Google Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2011-10-18 18:12:01 +00:00
|
|
|
package server
|
2011-06-17 03:45:47 +00:00
|
|
|
|
|
|
|
import (
|
2013-05-24 21:19:29 +00:00
|
|
|
"bufio"
|
2011-07-03 06:32:42 +00:00
|
|
|
"bytes"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2011-06-17 03:45:47 +00:00
|
|
|
"fmt"
|
|
|
|
"html"
|
2011-06-23 19:11:01 +00:00
|
|
|
"log"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"net/http"
|
2013-06-12 09:17:30 +00:00
|
|
|
"net/url"
|
2011-06-17 03:45:47 +00:00
|
|
|
"os"
|
2013-05-24 21:19:29 +00:00
|
|
|
"path/filepath"
|
2011-07-06 00:27:10 +00:00
|
|
|
"regexp"
|
2011-07-03 20:33:56 +00:00
|
|
|
"strconv"
|
2011-07-03 01:29:57 +00:00
|
|
|
"strings"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"time"
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
"camlistore.org/pkg/blob"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"camlistore.org/pkg/blobserver"
|
|
|
|
"camlistore.org/pkg/client" // just for NewUploadHandleFromString. move elsewhere?
|
2013-06-28 15:15:48 +00:00
|
|
|
"camlistore.org/pkg/fileembed"
|
2013-06-12 09:17:30 +00:00
|
|
|
"camlistore.org/pkg/httputil"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"camlistore.org/pkg/jsonconfig"
|
2012-12-29 14:51:42 +00:00
|
|
|
"camlistore.org/pkg/jsonsign/signhandler"
|
2013-05-24 21:19:29 +00:00
|
|
|
"camlistore.org/pkg/osutil"
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
"camlistore.org/pkg/schema"
|
|
|
|
"camlistore.org/pkg/search"
|
2013-06-20 21:58:12 +00:00
|
|
|
uistatic "camlistore.org/server/camlistored/ui"
|
2011-06-17 03:45:47 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// PublishHandler publishes your info to the world, if permanodes have
|
|
|
|
// appropriate ACLs set. (everything is private by default)
|
|
|
|
type PublishHandler struct {
|
2011-06-19 20:09:43 +00:00
|
|
|
RootName string
|
|
|
|
Search *search.Handler
|
|
|
|
Storage blobserver.Storage // of blobRoot
|
|
|
|
Cache blobserver.Storage // or nil
|
2011-10-25 20:30:02 +00:00
|
|
|
sc ScaledImage // cache of scaled images, optional
|
2011-07-05 19:10:35 +00:00
|
|
|
|
2011-07-08 17:03:53 +00:00
|
|
|
JSFiles, CSSFiles []string
|
|
|
|
|
2013-06-28 15:15:48 +00:00
|
|
|
handlerFinder blobserver.FindHandlerByTyper
|
|
|
|
|
|
|
|
// sourceRoot optionally specifies the path to root of Camlistore's
|
|
|
|
// source. If empty, the UI files must be compiled in to the
|
|
|
|
// binary (with go run make.go). This comes from the "sourceRoot"
|
|
|
|
// publish handler config option.
|
|
|
|
sourceRoot string
|
|
|
|
|
|
|
|
uiDir string // if sourceRoot != "", this is sourceRoot+"/server/camlistored/ui"
|
|
|
|
|
|
|
|
// closureHandler serves the Closure JS files.
|
2013-05-24 21:19:29 +00:00
|
|
|
closureHandler http.Handler
|
2011-06-17 03:45:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
blobserver.RegisterHandlerConstructor("publish", newPublishFromConfig)
|
|
|
|
}
|
|
|
|
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
func newPublishFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
|
2011-07-08 22:05:46 +00:00
|
|
|
ph := &PublishHandler{
|
2013-01-11 18:14:18 +00:00
|
|
|
handlerFinder: ld,
|
2011-07-08 22:05:46 +00:00
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
ph.RootName = conf.RequiredString("rootName")
|
2011-07-08 17:03:53 +00:00
|
|
|
ph.JSFiles = conf.OptionalList("js")
|
|
|
|
ph.CSSFiles = conf.OptionalList("css")
|
2011-06-17 03:45:47 +00:00
|
|
|
blobRoot := conf.RequiredString("blobRoot")
|
|
|
|
searchRoot := conf.RequiredString("searchRoot")
|
|
|
|
cachePrefix := conf.OptionalString("cache", "")
|
2011-10-25 20:30:02 +00:00
|
|
|
scType := conf.OptionalString("scaledImage", "")
|
2011-06-23 19:11:01 +00:00
|
|
|
bootstrapSignRoot := conf.OptionalString("devBootstrapPermanodeUsing", "")
|
2012-05-01 23:02:07 +00:00
|
|
|
rootNode := conf.OptionalList("rootPermanode")
|
2013-06-28 15:15:48 +00:00
|
|
|
ph.sourceRoot = conf.OptionalString("sourceRoot", "")
|
2011-06-17 03:45:47 +00:00
|
|
|
if err = conf.Validate(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
if ph.RootName == "" {
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
return nil, errors.New("invalid empty rootName")
|
2011-06-19 20:09:43 +00:00
|
|
|
}
|
|
|
|
|
2011-06-17 03:45:47 +00:00
|
|
|
bs, err := ld.GetStorage(blobRoot)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("publish handler's blobRoot of %q error: %v", blobRoot, err)
|
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
ph.Storage = bs
|
2011-06-17 03:45:47 +00:00
|
|
|
|
|
|
|
si, err := ld.GetHandler(searchRoot)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("publish handler's searchRoot of %q error: %v", searchRoot, err)
|
|
|
|
}
|
2011-06-23 19:11:01 +00:00
|
|
|
var ok bool
|
2011-07-03 17:18:54 +00:00
|
|
|
ph.Search, ok = si.(*search.Handler)
|
2011-06-23 19:11:01 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("publish handler's searchRoot of %q is of type %T, expecting a search handler",
|
|
|
|
searchRoot, si)
|
|
|
|
}
|
2011-06-23 05:26:11 +00:00
|
|
|
|
2012-05-01 23:02:07 +00:00
|
|
|
if rootNode != nil {
|
|
|
|
if len(rootNode) != 2 {
|
|
|
|
return nil, fmt.Errorf("rootPermanode config must contain the jsonSignerHandler and the permanode hash")
|
2011-06-23 19:11:01 +00:00
|
|
|
}
|
2012-05-01 23:02:07 +00:00
|
|
|
|
|
|
|
if t := ld.GetHandlerType(rootNode[0]); t != "jsonsign" {
|
|
|
|
return nil, fmt.Errorf("publish handler's rootPermanode first value not a jsonsign")
|
|
|
|
}
|
|
|
|
h, _ := ld.GetHandler(rootNode[0])
|
2012-12-29 14:51:42 +00:00
|
|
|
jsonSign := h.(*signhandler.Handler)
|
2013-08-04 02:54:30 +00:00
|
|
|
pn, ok := blob.Parse(rootNode[1])
|
|
|
|
if !ok {
|
2013-06-26 23:02:40 +00:00
|
|
|
return nil, fmt.Errorf("Invalid \"rootPermanode\" value; was expecting a blobRef, got %q.", rootNode[1])
|
|
|
|
}
|
2012-05-01 23:02:07 +00:00
|
|
|
if err := ph.setRootNode(jsonSign, pn); err != nil {
|
|
|
|
return nil, fmt.Errorf("error setting publish root permanode: %v", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if bootstrapSignRoot != "" {
|
|
|
|
if t := ld.GetHandlerType(bootstrapSignRoot); t != "jsonsign" {
|
|
|
|
return nil, fmt.Errorf("publish handler's devBootstrapPermanodeUsing must be of type jsonsign")
|
|
|
|
}
|
|
|
|
h, _ := ld.GetHandler(bootstrapSignRoot)
|
2012-12-29 14:51:42 +00:00
|
|
|
jsonSign := h.(*signhandler.Handler)
|
2012-05-01 23:02:07 +00:00
|
|
|
if err := ph.bootstrapPermanode(jsonSign); err != nil {
|
|
|
|
return nil, fmt.Errorf("error bootstrapping permanode: %v", err)
|
|
|
|
}
|
2011-06-23 19:11:01 +00:00
|
|
|
}
|
2011-06-23 05:26:11 +00:00
|
|
|
}
|
2011-06-17 03:45:47 +00:00
|
|
|
|
|
|
|
if cachePrefix != "" {
|
|
|
|
bs, err := ld.GetStorage(cachePrefix)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("publish handler's cache of %q error: %v", cachePrefix, err)
|
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
ph.Cache = bs
|
2011-10-25 20:30:02 +00:00
|
|
|
switch scType {
|
|
|
|
case "lrucache":
|
2013-06-12 09:05:01 +00:00
|
|
|
ph.sc = NewScaledImageLRU()
|
2011-10-25 20:30:02 +00:00
|
|
|
case "":
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unsupported publish handler's scType: %q ", scType)
|
|
|
|
}
|
2011-06-17 03:45:47 +00:00
|
|
|
}
|
|
|
|
|
2013-06-28 15:15:48 +00:00
|
|
|
if ph.sourceRoot == "" {
|
|
|
|
ph.sourceRoot = os.Getenv("CAMLI_DEV_CAMLI_ROOT")
|
|
|
|
}
|
|
|
|
if ph.sourceRoot != "" {
|
|
|
|
ph.uiDir = filepath.Join(ph.sourceRoot, filepath.FromSlash("server/camlistored/ui"))
|
|
|
|
// Ignore any fileembed files:
|
|
|
|
Files = &fileembed.Files{
|
|
|
|
DirFallback: filepath.Join(ph.sourceRoot, filepath.FromSlash("pkg/server")),
|
|
|
|
}
|
|
|
|
uistatic.Files = &fileembed.Files{
|
|
|
|
DirFallback: ph.uiDir,
|
|
|
|
}
|
2013-05-24 21:19:29 +00:00
|
|
|
}
|
|
|
|
|
2013-06-28 15:15:48 +00:00
|
|
|
ph.closureHandler, err = ph.makeClosureHandler(ph.sourceRoot)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf(`Invalid "sourceRoot" value of %q: %v"`, ph.sourceRoot, err)
|
|
|
|
}
|
2011-07-05 19:10:35 +00:00
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
return ph, nil
|
2011-06-17 03:45:47 +00:00
|
|
|
}
|
|
|
|
|
2013-06-28 15:15:48 +00:00
|
|
|
func (ph *PublishHandler) makeClosureHandler(root string) (http.Handler, error) {
|
|
|
|
return makeClosureHandler(root, "publish")
|
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (ph *PublishHandler) rootPermanode() (blob.Ref, error) {
|
2011-07-03 01:29:57 +00:00
|
|
|
// TODO: caching, but this can change over time (though
|
|
|
|
// probably rare). might be worth a 5 second cache or
|
|
|
|
// something in-memory? better invalidation story first would
|
|
|
|
// be nice.
|
2011-07-03 17:18:54 +00:00
|
|
|
br, err := ph.Search.Index().PermanodeOfSignerAttrValue(ph.Search.Owner(), "camliRoot", ph.RootName)
|
2011-07-03 01:29:57 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error: publish handler at serving root name %q has no configured permanode: %v",
|
2011-07-03 17:18:54 +00:00
|
|
|
ph.RootName, err)
|
2011-07-03 01:29:57 +00:00
|
|
|
}
|
|
|
|
return br, err
|
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (ph *PublishHandler) lookupPathTarget(root blob.Ref, suffix string) (blob.Ref, error) {
|
2011-07-05 19:10:35 +00:00
|
|
|
if suffix == "" {
|
|
|
|
return root, nil
|
|
|
|
}
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
path, err := ph.Search.Index().PathLookup(ph.Search.Owner(), root, suffix, time.Time{})
|
2011-07-03 17:18:54 +00:00
|
|
|
if err != nil {
|
2013-08-04 02:54:30 +00:00
|
|
|
return blob.Ref{}, err
|
2011-07-03 17:18:54 +00:00
|
|
|
}
|
2013-08-04 02:54:30 +00:00
|
|
|
if !path.Target.Valid() {
|
|
|
|
return blob.Ref{}, os.ErrNotExist
|
2011-07-03 17:18:54 +00:00
|
|
|
}
|
|
|
|
return path.Target, nil
|
2011-07-03 01:29:57 +00:00
|
|
|
}
|
|
|
|
|
2011-07-08 22:05:46 +00:00
|
|
|
func (ph *PublishHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) {
|
|
|
|
if !ph.ViewerIsOwner(req) {
|
|
|
|
discoveryHelper(rw, req, map[string]interface{}{
|
|
|
|
"error": "viewer isn't owner",
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
2013-01-11 18:14:18 +00:00
|
|
|
_, handler, err := ph.handlerFinder.FindHandlerByType("ui")
|
2011-07-08 22:05:46 +00:00
|
|
|
if err != nil {
|
|
|
|
discoveryHelper(rw, req, map[string]interface{}{
|
|
|
|
"error": "no admin handler running",
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ui := handler.(*UIHandler)
|
2012-11-07 17:57:43 +00:00
|
|
|
ui.root.serveDiscovery(rw, req)
|
2011-07-08 22:05:46 +00:00
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
func (ph *PublishHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
2011-07-08 22:05:46 +00:00
|
|
|
if req.URL.Query().Get("camli.mode") == "config" {
|
|
|
|
ph.serveDiscovery(rw, req)
|
|
|
|
return
|
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
preq := ph.NewRequest(rw, req)
|
|
|
|
preq.serveHTTP()
|
2011-07-03 06:32:42 +00:00
|
|
|
}
|
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
// publishRequest is the state around a single HTTP request to the
|
|
|
|
// publish handler
|
|
|
|
type publishRequest struct {
|
|
|
|
ph *PublishHandler
|
|
|
|
rw http.ResponseWriter
|
|
|
|
req *http.Request
|
|
|
|
base, suffix, subres string
|
2013-08-04 02:54:30 +00:00
|
|
|
rootpn blob.Ref
|
|
|
|
subject blob.Ref
|
2011-07-07 01:38:27 +00:00
|
|
|
inSubjectChain map[string]bool // blobref -> true
|
|
|
|
subjectBasePath string
|
2011-07-06 00:27:10 +00:00
|
|
|
|
|
|
|
// A describe request that we can reuse, sharing its map of
|
|
|
|
// blobs already described.
|
|
|
|
dr *search.DescribeRequest
|
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
func (ph *PublishHandler) NewRequest(rw http.ResponseWriter, req *http.Request) *publishRequest {
|
2011-07-03 06:02:11 +00:00
|
|
|
// splits a path request into its suffix and subresource parts.
|
|
|
|
// e.g. /blog/foo/camli/res/file/xxx -> ("foo", "file/xxx")
|
2013-06-12 09:17:30 +00:00
|
|
|
suffix, res := httputil.PathSuffix(req), ""
|
2011-07-05 19:10:35 +00:00
|
|
|
if strings.HasPrefix(suffix, "-/") {
|
|
|
|
suffix, res = "", suffix[2:]
|
|
|
|
} else if s := strings.SplitN(suffix, "/-/", 2); len(s) == 2 {
|
2011-07-03 01:29:57 +00:00
|
|
|
suffix, res = s[0], s[1]
|
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
rootpn, _ := ph.rootPermanode()
|
|
|
|
return &publishRequest{
|
2011-07-07 01:38:27 +00:00
|
|
|
ph: ph,
|
|
|
|
rw: rw,
|
|
|
|
req: req,
|
|
|
|
suffix: suffix,
|
2013-06-12 09:17:30 +00:00
|
|
|
base: httputil.PathBase(req),
|
2011-07-07 01:38:27 +00:00
|
|
|
subres: res,
|
|
|
|
rootpn: rootpn,
|
|
|
|
dr: ph.Search.NewDescribeRequest(),
|
|
|
|
inSubjectChain: make(map[string]bool),
|
|
|
|
subjectBasePath: "",
|
2011-07-03 06:02:11 +00:00
|
|
|
}
|
2011-07-03 01:29:57 +00:00
|
|
|
}
|
|
|
|
|
2011-07-08 22:05:46 +00:00
|
|
|
func (ph *PublishHandler) ViewerIsOwner(req *http.Request) bool {
|
|
|
|
// TODO: better check later
|
|
|
|
return strings.HasPrefix(req.RemoteAddr, "127.") ||
|
|
|
|
strings.HasPrefix(req.RemoteAddr, "localhost:")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *publishRequest) ViewerIsOwner() bool {
|
|
|
|
return pr.ph.ViewerIsOwner(pr.req)
|
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
func (pr *publishRequest) Debug() bool {
|
|
|
|
return pr.req.FormValue("debug") == "1"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *publishRequest) SubresourceType() string {
|
2011-07-07 01:38:27 +00:00
|
|
|
if len(pr.subres) >= 3 && strings.HasPrefix(pr.subres, "/=") {
|
|
|
|
return pr.subres[2:3]
|
2011-07-03 01:29:57 +00:00
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (pr *publishRequest) SubresFileURL(path []blob.Ref, fileName string) string {
|
2011-07-03 20:33:56 +00:00
|
|
|
return pr.SubresThumbnailURL(path, fileName, -1)
|
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (pr *publishRequest) SubresThumbnailURL(path []blob.Ref, fileName string, maxDimen int) string {
|
2011-07-03 17:18:54 +00:00
|
|
|
var buf bytes.Buffer
|
2011-07-07 01:38:27 +00:00
|
|
|
resType := "i"
|
2011-07-03 20:33:56 +00:00
|
|
|
if maxDimen == -1 {
|
2011-07-07 01:38:27 +00:00
|
|
|
resType = "f"
|
|
|
|
}
|
|
|
|
fmt.Fprintf(&buf, "%s", pr.subjectBasePath)
|
|
|
|
if !strings.Contains(pr.subjectBasePath, "/-/") {
|
|
|
|
buf.Write([]byte("/-"))
|
2011-07-03 20:33:56 +00:00
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
for _, br := range path {
|
2011-07-07 01:38:27 +00:00
|
|
|
if pr.inSubjectChain[br.String()] {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fmt.Fprintf(&buf, "/h%s", br.DigestPrefix(10))
|
2011-07-03 01:29:57 +00:00
|
|
|
}
|
2011-07-07 01:38:27 +00:00
|
|
|
fmt.Fprintf(&buf, "/=%s", resType)
|
2011-08-25 15:14:47 +00:00
|
|
|
fmt.Fprintf(&buf, "/%s", url.QueryEscape(fileName))
|
2011-07-03 20:33:56 +00:00
|
|
|
if maxDimen != -1 {
|
|
|
|
fmt.Fprintf(&buf, "?mw=%d&mh=%d", maxDimen, maxDimen)
|
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
return buf.String()
|
2011-07-03 01:29:57 +00:00
|
|
|
}
|
2011-06-17 03:45:47 +00:00
|
|
|
|
2011-07-07 01:38:27 +00:00
|
|
|
var memberRE = regexp.MustCompile(`^/?h([0-9a-f]+)`)
|
2011-07-06 00:27:10 +00:00
|
|
|
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
func (pr *publishRequest) findSubject() error {
|
2011-07-07 01:38:27 +00:00
|
|
|
if strings.HasPrefix(pr.suffix, "=s/") {
|
|
|
|
pr.subres = "/" + pr.suffix
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
subject, err := pr.ph.lookupPathTarget(pr.rootpn, pr.suffix)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-07-01 15:27:43 +00:00
|
|
|
if strings.HasPrefix(pr.subres, "=z/") {
|
|
|
|
// this happens when we are at the root of the published path,
|
|
|
|
// e.g /base/suffix/-/=z/foo.zip
|
|
|
|
// so we need to reset subres as fullpath so that it is detected
|
|
|
|
// properly when switching on pr.SubresourceType()
|
|
|
|
pr.subres = "/" + pr.subres
|
|
|
|
// since we return early, we set the subject because that is
|
|
|
|
// what is going to be used as a root node by the zip handler.
|
|
|
|
pr.subject = subject
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-07-07 01:38:27 +00:00
|
|
|
pr.inSubjectChain[subject.String()] = true
|
|
|
|
pr.subjectBasePath = pr.base + pr.suffix
|
2011-07-06 00:27:10 +00:00
|
|
|
|
2011-07-07 01:38:27 +00:00
|
|
|
// Chase /h<xxxxx> hops in suffix.
|
2011-07-06 00:27:10 +00:00
|
|
|
for {
|
|
|
|
m := memberRE.FindStringSubmatch(pr.subres)
|
|
|
|
if m == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
match, memberPrefix := m[0], m[1]
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error looking up potential member %q in describe of subject %q: %v",
|
|
|
|
memberPrefix, subject, err)
|
|
|
|
}
|
2011-07-07 01:38:27 +00:00
|
|
|
|
|
|
|
subject, err = pr.ph.Search.ResolvePrefixHop(subject, memberPrefix)
|
2011-07-06 00:27:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2011-07-07 01:38:27 +00:00
|
|
|
pr.inSubjectChain[subject.String()] = true
|
2011-07-06 00:27:10 +00:00
|
|
|
pr.subres = pr.subres[len(match):]
|
2011-07-07 01:38:27 +00:00
|
|
|
pr.subjectBasePath = addPathComponent(pr.subjectBasePath, match)
|
2011-07-06 00:27:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pr.subject = subject
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
func (pr *publishRequest) serveHTTP() {
|
2013-08-04 02:54:30 +00:00
|
|
|
if !pr.rootpn.Valid() {
|
2011-07-03 17:18:54 +00:00
|
|
|
pr.rw.WriteHeader(404)
|
2011-06-23 19:11:01 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
if pr.Debug() {
|
|
|
|
pr.pf("I am publish handler at base %q, serving root %q (permanode=%s), suffix %q, subreq %q<hr>",
|
|
|
|
pr.base, pr.ph.RootName, pr.rootpn, html.EscapeString(pr.suffix), html.EscapeString(pr.subres))
|
2011-07-03 00:16:30 +00:00
|
|
|
}
|
2011-07-06 00:27:10 +00:00
|
|
|
|
|
|
|
if err := pr.findSubject(); err != nil {
|
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
|
|
|
if err == os.ErrNotExist {
|
2011-07-03 17:18:54 +00:00
|
|
|
pr.rw.WriteHeader(404)
|
2011-07-03 01:29:57 +00:00
|
|
|
return
|
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
log.Printf("Error looking up %s/%q: %v", pr.rootpn, pr.suffix, err)
|
|
|
|
pr.rw.WriteHeader(500)
|
2011-06-28 01:42:00 +00:00
|
|
|
return
|
|
|
|
}
|
2011-07-03 01:29:57 +00:00
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
if pr.Debug() {
|
|
|
|
pr.pf("<p><b>Subject:</b> <a href='/ui/?p=%s'>%s</a></p>", pr.subject, pr.subject)
|
2011-07-03 00:16:30 +00:00
|
|
|
return
|
|
|
|
}
|
2011-07-02 16:06:34 +00:00
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
switch pr.SubresourceType() {
|
|
|
|
case "":
|
2011-07-06 00:27:10 +00:00
|
|
|
pr.serveSubject()
|
2011-07-07 01:38:27 +00:00
|
|
|
case "b":
|
2011-07-03 17:18:54 +00:00
|
|
|
// TODO: download a raw blob
|
2011-07-07 01:38:27 +00:00
|
|
|
case "f": // file download
|
2011-07-03 19:28:39 +00:00
|
|
|
pr.serveSubresFileDownload()
|
2011-07-07 01:38:27 +00:00
|
|
|
case "i": // image, scaled
|
2011-07-03 20:33:56 +00:00
|
|
|
pr.serveSubresImage()
|
2011-07-07 01:38:27 +00:00
|
|
|
case "s": // static
|
|
|
|
pr.req.URL.Path = pr.subres[len("/=s"):]
|
2013-06-28 15:15:48 +00:00
|
|
|
if len(pr.req.URL.Path) <= 1 {
|
|
|
|
http.Error(pr.rw, "Illegal URL.", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
file := pr.req.URL.Path[1:]
|
|
|
|
if m := closurePattern.FindStringSubmatch(file); m != nil {
|
|
|
|
pr.req.URL.Path = "/" + m[1]
|
|
|
|
pr.ph.closureHandler.ServeHTTP(pr.rw, pr.req)
|
|
|
|
return
|
2013-05-24 21:19:29 +00:00
|
|
|
}
|
2013-07-04 23:07:50 +00:00
|
|
|
if file == "deps.js" {
|
2013-06-28 15:15:48 +00:00
|
|
|
serveDepsJS(pr.rw, pr.req, pr.ph.uiDir)
|
|
|
|
return
|
2013-05-24 21:19:29 +00:00
|
|
|
}
|
2013-06-28 15:15:48 +00:00
|
|
|
serveStaticFile(pr.rw, pr.req, uistatic.Files, file)
|
2013-07-01 15:27:43 +00:00
|
|
|
case "z":
|
|
|
|
pr.serveZip()
|
2011-07-03 01:29:57 +00:00
|
|
|
default:
|
2011-07-03 17:18:54 +00:00
|
|
|
pr.rw.WriteHeader(400)
|
|
|
|
pr.pf("<p>Invalid or unsupported resource request.</p>")
|
2011-07-03 01:29:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
func (pr *publishRequest) pf(format string, args ...interface{}) {
|
|
|
|
fmt.Fprintf(pr.rw, format, args...)
|
|
|
|
}
|
|
|
|
|
2011-07-05 19:10:35 +00:00
|
|
|
func (pr *publishRequest) staticPath(fileName string) string {
|
2011-07-07 01:38:27 +00:00
|
|
|
return pr.base + "=s/" + fileName
|
2011-07-05 19:10:35 +00:00
|
|
|
}
|
|
|
|
|
2011-07-07 01:38:27 +00:00
|
|
|
func addPathComponent(base, addition string) string {
|
|
|
|
if !strings.HasPrefix(addition, "/") {
|
|
|
|
addition = "/" + addition
|
2011-07-06 00:27:10 +00:00
|
|
|
}
|
2011-07-07 01:38:27 +00:00
|
|
|
if strings.Contains(base, "/-/") {
|
|
|
|
return base + addition
|
|
|
|
}
|
|
|
|
return base + "/-" + addition
|
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (pr *publishRequest) memberPath(member blob.Ref) string {
|
2011-07-07 01:38:27 +00:00
|
|
|
return addPathComponent(pr.subjectBasePath, "/h"+member.DigestPrefix(10))
|
2011-07-06 00:27:10 +00:00
|
|
|
}
|
|
|
|
|
2013-05-24 21:19:29 +00:00
|
|
|
var provCamliRx = regexp.MustCompile(`^goog\.(provide)\(['"]camlistore\.(.*)['"]\)`)
|
|
|
|
|
|
|
|
// camliClosurePage checks if filename is a .js file using closure
|
|
|
|
// and if yes, if it provides a page in the camlistore namespace.
|
|
|
|
// It returns that page name, or the empty string otherwise.
|
|
|
|
func camliClosurePage(filename string) string {
|
|
|
|
camliRootPath, err := osutil.GoPackagePath("camlistore.org")
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
2013-06-12 15:49:35 +00:00
|
|
|
fullpath := filepath.Join(camliRootPath, "server", "camlistored", "ui", filename)
|
2013-05-24 21:19:29 +00:00
|
|
|
f, err := os.Open(fullpath)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
br := bufio.NewReader(f)
|
|
|
|
for {
|
|
|
|
l, err := br.ReadString('\n')
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(l, "goog.") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
m := provCamliRx.FindStringSubmatch(l)
|
|
|
|
if m != nil {
|
|
|
|
return m[2]
|
|
|
|
}
|
|
|
|
}
|
2013-06-11 09:50:56 +00:00
|
|
|
return ""
|
2013-05-24 21:19:29 +00:00
|
|
|
}
|
|
|
|
|
2013-07-01 15:27:43 +00:00
|
|
|
// serveZip streams a zip archive of all the files "under"
|
|
|
|
// pr.subject. That is, all the files pointed by file permanodes,
|
|
|
|
// which are directly members of pr.subject or recursively down
|
|
|
|
// directory permanodes and permanodes members.
|
|
|
|
func (pr *publishRequest) serveZip() {
|
|
|
|
filename := ""
|
|
|
|
if len(pr.subres) > len("/=z/") {
|
|
|
|
filename = pr.subres[4:]
|
|
|
|
}
|
|
|
|
zh := &zipHandler{
|
|
|
|
fetcher: pr.ph.Storage,
|
|
|
|
search: pr.ph.Search,
|
|
|
|
root: pr.subject,
|
|
|
|
filename: filename,
|
|
|
|
}
|
|
|
|
zh.ServeHTTP(pr.rw, pr.req)
|
|
|
|
}
|
|
|
|
|
2013-07-08 22:00:34 +00:00
|
|
|
// serveHeader serves the html header with the relevant title, javascript
|
|
|
|
// and css includes. It is meant to be called from serveSubject.
|
|
|
|
func (pr *publishRequest) serveHeader(title, camliClosurePage string) {
|
|
|
|
pr.pf("<!doctype html>\n<html>\n<head>\n <title>%s</title>\n", html.EscapeString(title))
|
|
|
|
if camliClosurePage != "" && pr.ViewerIsOwner() {
|
|
|
|
pr.pf(" <script src='%s'></script>\n", pr.staticPath("closure/goog/base.js"))
|
|
|
|
pr.pf(" <script src='%s'></script>\n", pr.staticPath("deps.js"))
|
|
|
|
pr.pf(" <script src='%s'></script>\n", pr.base+"?camli.mode=config&var=CAMLISTORE_CONFIG")
|
|
|
|
pr.pf(" <script src='%s'></script>\n", pr.staticPath("base64.js"))
|
|
|
|
pr.pf(" <script src='%s'></script>\n", pr.staticPath("Crypto.js"))
|
|
|
|
pr.pf(" <script src='%s'></script>\n", pr.staticPath("SHA1.js"))
|
|
|
|
pr.pf("<script>\n goog.require('camlistore.%s');\n </script>\n", camliClosurePage)
|
|
|
|
}
|
|
|
|
for _, filename := range pr.ph.CSSFiles {
|
|
|
|
pr.pf(" <link rel='stylesheet' type='text/css' href='%s'>\n", pr.staticPath(filename))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// serveMeta serves all the described meta data about the published items,
|
|
|
|
// within the html header. It is meant to be called from serveSubject.
|
|
|
|
func (pr *publishRequest) serveMeta(des map[string]*search.DescribedBlob) {
|
|
|
|
pr.pf(" <script>\n")
|
|
|
|
pr.pf("var camliViewIsOwner = %v;\n", pr.ViewerIsOwner())
|
|
|
|
pr.pf("var camliPagePermanode = %q;\n", pr.subject)
|
|
|
|
pr.pf("var camliPageMeta = \n")
|
|
|
|
json, _ := json.MarshalIndent(des, "", " ")
|
|
|
|
pr.rw.Write(json)
|
|
|
|
pr.pf(";\n </script>\n</head>\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(mpl): use those everywhere else
|
|
|
|
const (
|
|
|
|
resSeparator = "/-"
|
|
|
|
digestPrefix = "h"
|
|
|
|
digestLen = 10
|
|
|
|
)
|
|
|
|
|
|
|
|
var hopRE = regexp.MustCompile(fmt.Sprintf("^/%s([0-9a-f]{%d})", digestPrefix, digestLen))
|
|
|
|
|
|
|
|
// publishedPath is a URL suffix path of the kind
|
|
|
|
// suffix + resSeparator + subresource(s), such as:
|
|
|
|
// /foo/bar/-/subres1/subres2
|
|
|
|
type publishedPath string
|
|
|
|
|
|
|
|
// splitHops returns a slice containing the subresource(s)
|
|
|
|
// digests. For example, with /foo/bar/-/he0917e5bcf/h5f46bb454d
|
|
|
|
// it will yield []string{"e0917e5bcf", "5f46bb454d"}
|
|
|
|
func (p publishedPath) splitHops() []string {
|
|
|
|
ps := string(p)
|
|
|
|
var hops []string
|
|
|
|
if idx := strings.Index(ps, resSeparator); idx != -1 {
|
|
|
|
ps = ps[idx+len(resSeparator):]
|
|
|
|
}
|
|
|
|
matchLen := 1 + len(digestPrefix) + digestLen
|
|
|
|
for {
|
|
|
|
m := memberRE.FindStringSubmatch(ps)
|
|
|
|
if m == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
hops = append(hops, m[1])
|
|
|
|
ps = ps[matchLen:]
|
|
|
|
}
|
|
|
|
return hops
|
|
|
|
}
|
|
|
|
|
|
|
|
// parent returns the base path and the blobRef of pr.subject's parent.
|
|
|
|
// It returns an error if pr.subject or pr.subjectBasePath were not set
|
|
|
|
// properly (with findSubject), or if the parent was not found.
|
2013-08-04 02:54:30 +00:00
|
|
|
func (pr *publishRequest) parent() (parentPath string, parentBlobRef blob.Ref, err error) {
|
|
|
|
if !pr.subject.Valid() {
|
|
|
|
return "", blob.Ref{}, errors.New("subject not set")
|
2013-07-08 22:00:34 +00:00
|
|
|
}
|
|
|
|
if pr.subjectBasePath == "" {
|
2013-08-04 02:54:30 +00:00
|
|
|
return "", blob.Ref{}, errors.New("subjectBasePath not set")
|
2013-07-08 22:00:34 +00:00
|
|
|
}
|
|
|
|
hops := publishedPath(pr.subjectBasePath).splitHops()
|
|
|
|
if len(hops) == 0 {
|
2013-08-04 02:54:30 +00:00
|
|
|
return "", blob.Ref{}, errors.New("No subresource digest in subjectBasePath")
|
2013-07-08 22:00:34 +00:00
|
|
|
}
|
|
|
|
subjectDigest := hops[len(hops)-1]
|
|
|
|
if subjectDigest != pr.subject.DigestPrefix(digestLen) {
|
2013-08-04 02:54:30 +00:00
|
|
|
return "", blob.Ref{}, errors.New("subject digest not in subjectBasePath")
|
2013-07-08 22:00:34 +00:00
|
|
|
}
|
|
|
|
parentPath = strings.TrimSuffix(pr.subjectBasePath, "/"+digestPrefix+subjectDigest)
|
|
|
|
|
|
|
|
if len(hops) == 1 {
|
|
|
|
// the parent is the suffix, not one of the subresource hops
|
|
|
|
for br, _ := range pr.inSubjectChain {
|
|
|
|
if br != pr.subject.String() {
|
2013-08-04 02:54:30 +00:00
|
|
|
parentBlobRef = blob.ParseOrZero(br)
|
2013-07-08 22:00:34 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// nested collection(s)
|
|
|
|
parentDigest := hops[len(hops)-2]
|
|
|
|
for br, _ := range pr.inSubjectChain {
|
2013-08-04 02:54:30 +00:00
|
|
|
bref, ok := blob.Parse(br)
|
|
|
|
if !ok {
|
|
|
|
return "", blob.Ref{}, fmt.Errorf("Could not parse %q as blobRef", br)
|
2013-07-08 22:00:34 +00:00
|
|
|
}
|
|
|
|
if bref.DigestPrefix(10) == parentDigest {
|
|
|
|
parentBlobRef = bref
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-08-04 02:54:30 +00:00
|
|
|
if !parentBlobRef.Valid() {
|
|
|
|
return "", blob.Ref{}, fmt.Errorf("No parent found for %v", pr.subjectBasePath)
|
2013-07-08 22:00:34 +00:00
|
|
|
}
|
|
|
|
return parentPath, parentBlobRef, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// serveNav serves some navigation links (prev, next, up) if the
|
|
|
|
// pr.subject is member of a collection (its parent has members).
|
|
|
|
// It is meant to be called from serveFile.
|
|
|
|
func (pr *publishRequest) serveNav() error {
|
|
|
|
// first get the parent path and blob
|
|
|
|
parentPath, parentbr, err := pr.parent()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Errors building nav links for %s: %v", pr.subject, err)
|
|
|
|
}
|
|
|
|
parentNav := fmt.Sprintf("[<a href='%s'>up</a>]", strings.TrimSuffix(parentPath, resSeparator))
|
|
|
|
|
|
|
|
// describe the parent so we get the siblings (members of the parent)
|
|
|
|
dr := pr.ph.Search.NewDescribeRequest()
|
|
|
|
dr.Describe(parentbr, 3)
|
|
|
|
parentRes, err := dr.Result()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Errors loading %s, permanode %s: %v, %#v", pr.req.URL, pr.subject, err, err)
|
|
|
|
}
|
|
|
|
members := parentRes[parentbr.String()].Members()
|
|
|
|
if len(members) == 0 {
|
|
|
|
pr.pf("<div class='camlifile'>[<a href='%s'>up</a>]</div>", parentNav)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
pos := 0
|
2013-08-04 02:54:30 +00:00
|
|
|
var prev, next blob.Ref
|
2013-07-08 22:00:34 +00:00
|
|
|
for k, member := range members {
|
|
|
|
if member.BlobRef.String() == pr.subject.String() {
|
|
|
|
pos = k
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if pos > 0 {
|
|
|
|
prev = members[pos-1].BlobRef
|
|
|
|
}
|
|
|
|
if pos < len(members)-1 {
|
|
|
|
next = members[pos+1].BlobRef
|
|
|
|
}
|
2013-08-04 02:54:30 +00:00
|
|
|
if prev.Valid() || next.Valid() {
|
2013-07-08 22:00:34 +00:00
|
|
|
var prevNav, nextNav string
|
2013-08-04 02:54:30 +00:00
|
|
|
if prev.Valid() {
|
2013-07-08 22:00:34 +00:00
|
|
|
prevNav = fmt.Sprintf("[<a href='%s/h%s'>prev</a>]",
|
|
|
|
parentPath, prev.DigestPrefix(10))
|
|
|
|
}
|
2013-08-04 02:54:30 +00:00
|
|
|
if next.Valid() {
|
2013-07-08 22:00:34 +00:00
|
|
|
nextNav = fmt.Sprintf("[<a href='%s/h%s'>next</a>]",
|
|
|
|
parentPath, next.DigestPrefix(10))
|
|
|
|
}
|
|
|
|
pr.pf("<div class='camlifile'>%s %s %s</div>", parentNav, prevNav, nextNav)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// serveFile serves the relevant view when the subject in serveSubject
|
|
|
|
// is a permanode with some content cref. It is meant to be called
|
|
|
|
// from serveSubject.
|
2013-08-04 02:54:30 +00:00
|
|
|
func (pr *publishRequest) serveFile(cref blob.Ref) error {
|
2013-07-08 22:00:34 +00:00
|
|
|
des, err := pr.dr.DescribeSync(cref)
|
|
|
|
if err != nil {
|
|
|
|
pr.pf("<p>Error serving file</p>")
|
|
|
|
return fmt.Errorf("Could not describe %v: %v", cref, err)
|
|
|
|
}
|
|
|
|
if des.File != nil {
|
2013-08-04 02:54:30 +00:00
|
|
|
path := []blob.Ref{pr.subject, cref}
|
2013-07-08 22:00:34 +00:00
|
|
|
downloadURL := pr.SubresFileURL(path, des.File.FileName)
|
|
|
|
pr.pf("<div>File: %s, %d bytes, type %s</div>",
|
|
|
|
html.EscapeString(des.File.FileName),
|
|
|
|
des.File.Size,
|
|
|
|
des.File.MIMEType)
|
|
|
|
if des.File.IsImage() {
|
|
|
|
pr.pf("<a href='%s'><img src='%s'></a>",
|
|
|
|
downloadURL,
|
|
|
|
pr.SubresThumbnailURL(path, des.File.FileName, 600))
|
|
|
|
}
|
|
|
|
pr.pf("<div id='%s' class='camlifile'>[<a href='%s'>download</a>]</div>",
|
|
|
|
cref.DomID(),
|
|
|
|
downloadURL)
|
|
|
|
}
|
|
|
|
if strings.Contains(pr.subjectBasePath, resSeparator) {
|
|
|
|
// this permanode has a "parent" collection.
|
|
|
|
// so we send a deep request on the parent in order to get some info
|
|
|
|
// about the siblings and build some "prev" and "next" nav links.
|
|
|
|
// TODO(mpl): nav links everywhere, not just when showing a permanode
|
|
|
|
// with some content.
|
|
|
|
err := pr.serveNav()
|
|
|
|
if err != nil {
|
|
|
|
pr.pf("<p>Error building navs links</p>")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// serveMembers serves the relevant view when the subject in serveSubject
|
|
|
|
// is a collection (permanode with members). It is meant to be called
|
|
|
|
// from serveSubject.
|
|
|
|
func (pr *publishRequest) serveMembers(title string, members []*search.DescribedBlob) {
|
|
|
|
zipName := ""
|
|
|
|
if title == "" {
|
|
|
|
zipName = "download.zip"
|
|
|
|
} else {
|
|
|
|
zipName = title + ".zip"
|
|
|
|
}
|
|
|
|
subjectPath := pr.subjectBasePath
|
|
|
|
if !strings.Contains(subjectPath, "/-/") {
|
|
|
|
subjectPath += "/-"
|
|
|
|
}
|
|
|
|
pr.pf("<div><a href='%s/=z/%s'>%s</a></div>\n", subjectPath,
|
|
|
|
html.EscapeString(url.QueryEscape(zipName)), html.EscapeString(zipName))
|
|
|
|
pr.pf("<ul id='members'>\n")
|
|
|
|
for _, member := range members {
|
|
|
|
des := member.Description()
|
|
|
|
if des != "" {
|
|
|
|
des = " - " + des
|
|
|
|
}
|
|
|
|
var fileLink, thumbnail string
|
|
|
|
if path, fileInfo, ok := member.PermanodeFile(); ok {
|
|
|
|
fileLink = fmt.Sprintf("<div id='%s' class='camlifile'><a href='%s'>file</a></div>",
|
|
|
|
path[len(path)-1].DomID(),
|
|
|
|
html.EscapeString(pr.SubresFileURL(path, fileInfo.FileName)),
|
|
|
|
)
|
|
|
|
if fileInfo.IsImage() {
|
|
|
|
thumbnail = fmt.Sprintf("<img src='%s'>", pr.SubresThumbnailURL(path, fileInfo.FileName, 200))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
memberTitle := member.Title()
|
|
|
|
if memberTitle == "" {
|
|
|
|
memberTitle = member.BlobRef.DigestPrefix(10)
|
|
|
|
}
|
|
|
|
pr.pf(" <li id='%s'><a href='%s'>%s<span>%s</span></a>%s%s</li>\n",
|
|
|
|
member.DomID(),
|
|
|
|
pr.memberPath(member.BlobRef),
|
|
|
|
thumbnail,
|
|
|
|
html.EscapeString(memberTitle),
|
|
|
|
des,
|
|
|
|
fileLink)
|
|
|
|
}
|
|
|
|
pr.pf("</ul>\n")
|
|
|
|
}
|
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
func (pr *publishRequest) serveSubject() {
|
2011-07-03 17:18:54 +00:00
|
|
|
dr := pr.ph.Search.NewDescribeRequest()
|
|
|
|
dr.Describe(pr.subject, 3)
|
2011-07-03 00:16:30 +00:00
|
|
|
res, err := dr.Result()
|
|
|
|
if err != nil {
|
2011-07-03 17:18:54 +00:00
|
|
|
log.Printf("Errors loading %s, permanode %s: %v, %#v", pr.req.URL, pr.subject, err, err)
|
|
|
|
pr.pf("<p>Errors loading.</p>")
|
2011-07-03 00:16:30 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-07-03 17:18:54 +00:00
|
|
|
subdes := res[pr.subject.String()]
|
2011-07-03 19:28:39 +00:00
|
|
|
|
|
|
|
if subdes.CamliType == "file" {
|
|
|
|
pr.serveFileDownload(subdes)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2011-07-03 01:29:57 +00:00
|
|
|
title := subdes.Title()
|
2011-07-03 00:16:30 +00:00
|
|
|
|
|
|
|
// HTML header + Javascript
|
2013-05-24 21:19:29 +00:00
|
|
|
var camliPage string
|
2013-07-08 22:00:34 +00:00
|
|
|
// TODO(mpl): We are only using the first .js file, and expecting it to be
|
|
|
|
// using closure. We want to be more customizable in the long run and enable
|
|
|
|
// some sort of templating mechanism.
|
|
|
|
if len(pr.ph.JSFiles) > 0 {
|
|
|
|
camliPage = camliClosurePage(pr.ph.JSFiles[0])
|
|
|
|
}
|
|
|
|
pr.serveHeader(title, camliPage)
|
|
|
|
pr.serveMeta(res)
|
|
|
|
pr.pf("<body>\n")
|
2011-07-03 00:16:30 +00:00
|
|
|
if title != "" {
|
2011-07-03 17:18:54 +00:00
|
|
|
pr.pf("<h1>%s</h1>\n", html.EscapeString(title))
|
2011-07-03 00:16:30 +00:00
|
|
|
}
|
2013-07-08 22:00:34 +00:00
|
|
|
defer pr.pf("</body>\n</html>\n")
|
2011-07-03 00:16:30 +00:00
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
if cref, ok := subdes.ContentRef(); ok {
|
2013-07-08 22:00:34 +00:00
|
|
|
err = pr.serveFile(cref)
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
return
|
2013-07-01 15:27:43 +00:00
|
|
|
}
|
2013-07-08 22:00:34 +00:00
|
|
|
} else {
|
|
|
|
if members := subdes.Members(); len(members) > 0 {
|
|
|
|
pr.serveMembers(title, members)
|
2013-07-01 15:27:43 +00:00
|
|
|
}
|
2011-07-02 16:06:34 +00:00
|
|
|
}
|
2013-05-24 21:19:29 +00:00
|
|
|
|
2013-06-30 20:45:16 +00:00
|
|
|
if camliPage != "" && pr.ViewerIsOwner() {
|
2013-05-24 21:19:29 +00:00
|
|
|
pr.pf("<script>\n")
|
|
|
|
pr.pf("var page = new camlistore.%s(CAMLISTORE_CONFIG);\n", camliPage)
|
|
|
|
pr.pf("page.decorate(document.body);\n")
|
|
|
|
pr.pf("</script>\n")
|
|
|
|
}
|
2011-06-23 19:11:01 +00:00
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (pr *publishRequest) validPathChain(path []blob.Ref) bool {
|
2011-07-03 19:28:39 +00:00
|
|
|
bi := pr.subject
|
|
|
|
for len(path) > 0 {
|
2013-08-04 02:54:30 +00:00
|
|
|
var next blob.Ref
|
2011-07-03 19:28:39 +00:00
|
|
|
next, path = path[0], path[1:]
|
2011-07-03 17:18:54 +00:00
|
|
|
|
2011-07-06 00:27:10 +00:00
|
|
|
desi, err := pr.dr.DescribeSync(bi)
|
2011-07-03 19:28:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !desi.HasSecureLinkTo(next) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
bi = next
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2011-07-03 20:33:56 +00:00
|
|
|
func (pr *publishRequest) serveSubresImage() {
|
|
|
|
params := pr.req.URL.Query()
|
|
|
|
mw, _ := strconv.Atoi(params.Get("mw"))
|
|
|
|
mh, _ := strconv.Atoi(params.Get("mh"))
|
2011-07-07 01:38:27 +00:00
|
|
|
des, err := pr.dr.DescribeSync(pr.subject)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error describing subject %q: %v", pr.subject, err)
|
|
|
|
return
|
2011-07-03 20:33:56 +00:00
|
|
|
}
|
2011-07-09 01:14:18 +00:00
|
|
|
pr.serveScaledImage(des, mw, mh, params.Get("square") == "1")
|
2011-07-03 20:33:56 +00:00
|
|
|
}
|
|
|
|
|
2011-07-03 19:28:39 +00:00
|
|
|
func (pr *publishRequest) serveSubresFileDownload() {
|
2011-07-07 01:38:27 +00:00
|
|
|
des, err := pr.dr.DescribeSync(pr.subject)
|
2011-07-03 19:28:39 +00:00
|
|
|
if err != nil {
|
2011-07-07 01:38:27 +00:00
|
|
|
log.Printf("error describing subject %q: %v", pr.subject, err)
|
2011-07-03 19:28:39 +00:00
|
|
|
return
|
|
|
|
}
|
2011-07-07 01:38:27 +00:00
|
|
|
pr.serveFileDownload(des)
|
2011-07-03 20:33:56 +00:00
|
|
|
}
|
|
|
|
|
2011-07-09 01:14:18 +00:00
|
|
|
func (pr *publishRequest) serveScaledImage(des *search.DescribedBlob, maxWidth, maxHeight int, square bool) {
|
2011-07-03 20:33:56 +00:00
|
|
|
fileref, _, ok := pr.fileSchemaRefFromBlob(des)
|
|
|
|
if !ok {
|
2011-07-08 20:33:55 +00:00
|
|
|
log.Printf("scaled image fail; failed to get file schema from des %q", des.BlobRef)
|
2011-07-03 20:33:56 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
th := &ImageHandler{
|
|
|
|
Fetcher: pr.ph.Storage,
|
|
|
|
Cache: pr.ph.Cache,
|
|
|
|
MaxWidth: maxWidth,
|
|
|
|
MaxHeight: maxHeight,
|
2011-07-09 01:14:18 +00:00
|
|
|
Square: square,
|
2011-10-25 20:30:02 +00:00
|
|
|
sc: pr.ph.sc,
|
2011-07-03 20:33:56 +00:00
|
|
|
}
|
|
|
|
th.ServeHTTP(pr.rw, pr.req, fileref)
|
2011-07-03 19:28:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pr *publishRequest) serveFileDownload(des *search.DescribedBlob) {
|
2011-07-03 20:33:56 +00:00
|
|
|
fileref, fileinfo, ok := pr.fileSchemaRefFromBlob(des)
|
|
|
|
if !ok {
|
2011-07-07 01:38:27 +00:00
|
|
|
log.Printf("Didn't get file schema from described blob %q", des.BlobRef)
|
2011-07-03 20:33:56 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
mime := ""
|
|
|
|
if fileinfo != nil && fileinfo.IsImage() {
|
2013-02-18 19:16:13 +00:00
|
|
|
mime = fileinfo.MIMEType
|
2011-07-03 20:33:56 +00:00
|
|
|
}
|
|
|
|
dh := &DownloadHandler{
|
|
|
|
Fetcher: pr.ph.Storage,
|
|
|
|
Cache: pr.ph.Cache,
|
|
|
|
ForceMime: mime,
|
|
|
|
}
|
|
|
|
dh.ServeHTTP(pr.rw, pr.req, fileref)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Given a described blob, optionally follows a camliContent and
|
|
|
|
// returns the file's schema blobref and its fileinfo (if found).
|
2013-08-04 02:54:30 +00:00
|
|
|
func (pr *publishRequest) fileSchemaRefFromBlob(des *search.DescribedBlob) (fileref blob.Ref, fileinfo *search.FileInfo, ok bool) {
|
2011-07-03 19:28:39 +00:00
|
|
|
if des == nil {
|
|
|
|
http.NotFound(pr.rw, pr.req)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if des.Permanode != nil {
|
|
|
|
// TODO: get "forceMime" attr out of the permanode? or
|
|
|
|
// fileName content-disposition?
|
|
|
|
if cref := des.Permanode.Attr.Get("camliContent"); cref != "" {
|
2013-08-04 02:54:30 +00:00
|
|
|
cbr, ok2 := blob.Parse(cref)
|
|
|
|
if !ok2 {
|
2011-07-03 19:28:39 +00:00
|
|
|
http.Error(pr.rw, "bogus camliContent", 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
des = des.PeerBlob(cbr)
|
|
|
|
if des == nil {
|
|
|
|
http.Error(pr.rw, "camliContent not a peer in describe", 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-07-03 20:33:56 +00:00
|
|
|
if des.CamliType == "file" {
|
|
|
|
return des.BlobRef, des.File, true
|
2011-07-03 19:28:39 +00:00
|
|
|
}
|
2011-07-03 20:33:56 +00:00
|
|
|
http.Error(pr.rw, "failed to find fileSchemaRefFromBlob", 404)
|
|
|
|
return
|
2011-07-03 17:18:54 +00:00
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (ph *PublishHandler) signUpload(jsonSign *signhandler.Handler, name string, bb *schema.Builder) (blob.Ref, error) {
|
2013-01-22 04:56:12 +00:00
|
|
|
signed, err := jsonSign.Sign(bb)
|
2012-05-01 23:02:07 +00:00
|
|
|
if err != nil {
|
2013-08-04 02:54:30 +00:00
|
|
|
return blob.Ref{}, fmt.Errorf("error signing %s: %v", name, err)
|
2012-05-01 23:02:07 +00:00
|
|
|
}
|
|
|
|
uh := client.NewUploadHandleFromString(signed)
|
|
|
|
_, err = ph.Storage.ReceiveBlob(uh.BlobRef, uh.Contents)
|
|
|
|
if err != nil {
|
2013-08-04 02:54:30 +00:00
|
|
|
return blob.Ref{}, fmt.Errorf("error uploading %s: %v", name, err)
|
2012-05-01 23:02:07 +00:00
|
|
|
}
|
|
|
|
return uh.BlobRef, nil
|
|
|
|
}
|
|
|
|
|
2013-08-04 02:54:30 +00:00
|
|
|
func (ph *PublishHandler) setRootNode(jsonSign *signhandler.Handler, pn blob.Ref) (err error) {
|
2012-05-01 23:02:07 +00:00
|
|
|
_, err = ph.signUpload(jsonSign, "set-attr camliRoot", schema.NewSetAttributeClaim(pn, "camliRoot", ph.RootName))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = ph.signUpload(jsonSign, "set-attr title", schema.NewSetAttributeClaim(pn, "title", "Publish root node for "+ph.RootName))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2012-12-29 14:51:42 +00:00
|
|
|
func (ph *PublishHandler) bootstrapPermanode(jsonSign *signhandler.Handler) (err error) {
|
2011-07-03 17:18:54 +00:00
|
|
|
if pn, err := ph.Search.Index().PermanodeOfSignerAttrValue(ph.Search.Owner(), "camliRoot", ph.RootName); err == nil {
|
|
|
|
log.Printf("Publish root %q using existing permanode %s", ph.RootName, pn)
|
2011-06-23 19:11:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
2011-07-03 17:18:54 +00:00
|
|
|
log.Printf("Publish root %q needs a permanode + claim", ph.RootName)
|
2011-06-23 19:11:01 +00:00
|
|
|
|
2012-05-01 23:02:07 +00:00
|
|
|
pn, err := ph.signUpload(jsonSign, "permanode", schema.NewUnsignedPermanode())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2011-06-23 19:11:01 +00:00
|
|
|
}
|
2012-05-01 23:02:07 +00:00
|
|
|
err = ph.setRootNode(jsonSign, pn)
|
|
|
|
return err
|
2011-06-17 03:45:47 +00:00
|
|
|
}
|