func tearDownTestDB(t *testing.T, db *Database) { err := db.Delete() status, _ := base.ErrorAsHTTPStatus(err) if status != 200 && status != 404 { assertNoError(t, err, "Couldn't delete database 'db'") } }
func renderError(err error, r http.ResponseWriter) { status, message := base.ErrorAsHTTPStatus(err) r.Header().Set("Content-Type", "application/json") r.WriteHeader(status) jsonOut, _ := json.Marshal(map[string]interface{}{"error": status, "reason": message}) r.Write(jsonOut) }
// Sets the database object's channelMapper and validator based on the JS code in _design/channels func (db *Database) ReadDesignDocument() error { body, err := db.Get("_design/channels") if err != nil { if status, _ := base.ErrorAsHTTPStatus(err); status == http.StatusNotFound { err = nil // missing design document is not an error } return err } src, ok := body["channelmap"].(string) if ok { log.Printf("Channel mapper = %s", src) db.ChannelMapper, err = channels.NewChannelMapper(src) if err != nil { log.Printf("WARNING: Error loading channel mapper: %s", err) return err } } src, ok = body["validate_doc_update"].(string) if ok { log.Printf("Validator = %s", src) db.Validator, err = NewValidator(src) if err != nil { log.Printf("WARNING: Error loading validator: %s", err) return err } } return nil }
// HTTP handler for a POST to _bulk_get // Request looks like POST /db/_bulk_get?revs=___&attachments=___ // where the boolean ?revs parameter adds a revision history to each doc // and the boolean ?attachments parameter includes attachment bodies. // The body of the request is JSON and looks like: // { // "docs": [ // {"id": "docid", "rev": "revid", "atts_since": [12,...]}, ... // ] // } func (h *handler) handleBulkGet() error { includeRevs := h.getBoolQuery("revs") includeAttachments := h.getBoolQuery("attachments") body, err := h.readJSON() if err != nil { return err } err = h.writeMultipart(func(writer *multipart.Writer) error { for _, item := range body["docs"].([]interface{}) { doc := item.(map[string]interface{}) docid, _ := doc["id"].(string) revid := "" revok := true if doc["rev"] != nil { revid, revok = doc["rev"].(string) } if docid == "" || !revok { return base.HTTPErrorf(http.StatusBadRequest, "Invalid doc/rev ID") } var attsSince []string = nil if includeAttachments { if doc["atts_since"] != nil { raw, ok := doc["atts_since"].([]interface{}) if ok { attsSince = make([]string, len(raw)) for i := 0; i < len(raw); i++ { attsSince[i], ok = raw[i].(string) if !ok { break } } } if !ok { return base.HTTPErrorf(http.StatusBadRequest, "Invalid atts_since") } } else { attsSince = []string{} } } body, err := h.db.GetRev(docid, revid, includeRevs, attsSince) if err != nil { status, reason := base.ErrorAsHTTPStatus(err) errStr := base.CouchHTTPErrorName(status) body = db.Body{"id": docid, "error": errStr, "reason": reason, "status": status} if revid != "" { body["rev"] = revid } } h.db.WriteRevisionAsPart(body, err != nil, writer) } return nil }) return err }
// Starts a simple REST listener that will get and set user credentials. func StartAuthListener(addr string, auth *auth.Authenticator) { handler := func(r http.ResponseWriter, rq *http.Request) { username := rq.URL.Path[1:] method := rq.Method log.Printf("AUTH: %s %q", method, username) var err error if rq.URL.Path == "/" { // Root URL: Supports POSTing user info switch method { case "POST": err = putUser(r, rq, auth, "") default: err = kBadMethodError } } else if username == "_session" { // /_session: Generate login session for user switch method { case "POST": err = createUserSession(r, rq, auth) default: err = kBadMethodError } } else { // Otherwise: Interpret path as username. if username == "GUEST" { username = "" } switch method { case "GET": user, _ := auth.GetUser(username) if user == nil { err = kNotFoundError break } bytes, _ := json.Marshal(user) r.Write(bytes) case "PUT": err = putUser(r, rq, auth, username) case "DELETE": user, _ := auth.GetUser(username) if user == nil || auth.DeleteUser(user) != nil { err = kNotFoundError } default: err = kBadMethodError } } if err != nil { status, message := base.ErrorAsHTTPStatus(err) r.WriteHeader(status) r.Header().Set("Content-Type", "application/json") r.WriteHeader(status) jsonOut, _ := json.Marshal(map[string]interface{}{"error": status, "reason": message}) r.Write(jsonOut) } } go http.ListenAndServe(addr, http.HandlerFunc(handler)) }
// HTTP handler for a POST to _bulk_get func (h *handler) handleBulkGet() error { includeRevs := h.getBoolQuery("revs") includeAttachments := h.getBoolQuery("attachments") body, err := h.readJSON() if err != nil { return err } result := make([]db.Body, 0, 5) for _, item := range body["docs"].([]interface{}) { doc := item.(map[string]interface{}) docid, _ := doc["id"].(string) revid := "" revok := true if doc["rev"] != nil { revid, revok = doc["rev"].(string) } if docid == "" || !revok { return &base.HTTPError{http.StatusBadRequest, "Invalid doc/rev ID"} } var attsSince []string = nil if includeAttachments { if doc["atts_since"] != nil { raw, ok := doc["atts_since"].([]interface{}) if ok { attsSince = make([]string, len(raw)) for i := 0; i < len(raw); i++ { attsSince[i], ok = raw[i].(string) if !ok { break } } } if !ok { return &base.HTTPError{http.StatusBadRequest, "Invalid atts_since"} } } else { attsSince = []string{} } } body, err := h.db.GetRev(docid, revid, includeRevs, attsSince) if err != nil { status, msg := base.ErrorAsHTTPStatus(err) body = db.Body{"id": docid, "error": msg, "status": status} if revid != "" { body["rev"] = revid } } result = append(result, body) } h.writeJSONStatus(http.StatusOK, result) return nil }
// HTTP handler for a POST to _bulk_docs func (h *handler) handleBulkDocs() error { body, err := h.readJSON() if err != nil { return err } newEdits, ok := body["new_edits"].(bool) if !ok { newEdits = true } docs := body["docs"].([]interface{}) h.db.ReserveSequences(uint64(len(docs))) result := make([]db.Body, 0, len(docs)) for _, item := range docs { doc := item.(map[string]interface{}) docid, _ := doc["_id"].(string) var err error var revid string if newEdits { if docid != "" { revid, err = h.db.Put(docid, doc) } else { docid, revid, err = h.db.Post(doc) } } else { revisions := db.ParseRevisions(doc) if revisions == nil { err = &base.HTTPError{http.StatusBadRequest, "Bad _revisions"} } else { revid = revisions[0] err = h.db.PutExistingRev(docid, doc, revisions) } } status := db.Body{} if docid != "" { status["id"] = docid } if err != nil { code, msg := base.ErrorAsHTTPStatus(err) status["status"] = code status["error"] = base.CouchHTTPErrorName(code) status["reason"] = msg base.Log("\tBulkDocs: Doc %q --> %v", docid, err) err = nil // wrote it to output already; not going to return it } else { status["rev"] = revid } result = append(result, status) } h.writeJSONStatus(http.StatusCreated, result) return nil }
func (sc *ServerContext) installPrincipals(context *db.DatabaseContext, spec map[string]*PrincipalConfig, what string) error { for name, princ := range spec { princ.Name = &name _, err := updatePrincipal(context, *princ, (what == "user"), (name == "GUEST")) if err != nil { // A conflict error just means updatePrincipal didn't overwrite an existing user. if status, _ := base.ErrorAsHTTPStatus(err); status != http.StatusConflict { return fmt.Errorf("Couldn't create %s %q: %v", what, name, err) } } else if name == "GUEST" { base.Log(" Reset guest user to config") } else { base.Log(" Created %s %q", what, name) } } return nil }
func TestDatabase(t *testing.T) { db := setupTestDB(t) defer tearDownTestDB(t, db) // Test creating & updating a document: log.Printf("Create rev 1...") body := Body{"key1": "value1", "key2": 1234} rev1id, err := db.Put("doc1", body) assertNoError(t, err, "Couldn't create document") assert.Equals(t, rev1id, body["_rev"]) assert.Equals(t, rev1id, "1-cb0c9a22be0e5a1b01084ec019defa81") log.Printf("Create rev 2...") body["key1"] = "new value" body["key2"] = int64(4321) rev2id, err := db.Put("doc1", body) body["_id"] = "doc1" assertNoError(t, err, "Couldn't update document") assert.Equals(t, rev2id, body["_rev"]) assert.Equals(t, rev2id, "2-488724414d0ed6b398d6d2aeb228d797") // Retrieve the document: log.Printf("Retrieve doc...") gotbody, err := db.Get("doc1") assertNoError(t, err, "Couldn't get document") assert.DeepEquals(t, gotbody, body) log.Printf("Retrieve rev 1...") gotbody, err = db.GetRev("doc1", rev1id, false, nil) assertNoError(t, err, "Couldn't get document with rev 1") assert.DeepEquals(t, gotbody, Body{"key1": "value1", "key2": int64(1234), "_id": "doc1", "_rev": rev1id}) log.Printf("Retrieve rev 2...") gotbody, err = db.GetRev("doc1", rev2id, false, nil) assertNoError(t, err, "Couldn't get document with rev") assert.DeepEquals(t, gotbody, body) gotbody, err = db.GetRev("doc1", "bogusrev", false, nil) status, _ := base.ErrorAsHTTPStatus(err) assert.Equals(t, status, 404) // Test the _revisions property: log.Printf("Check _revisions...") gotbody, err = db.GetRev("doc1", rev2id, true, nil) revisions := gotbody["_revisions"].(Body) assert.Equals(t, revisions["start"], 2) assert.DeepEquals(t, revisions["ids"], []string{"488724414d0ed6b398d6d2aeb228d797", "cb0c9a22be0e5a1b01084ec019defa81"}) // Test RevDiff: log.Printf("Check RevDiff...") missing, possible, err := db.RevDiff("doc1", []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "2-488724414d0ed6b398d6d2aeb228d797"}) assertNoError(t, err, "RevDiff failed") assert.True(t, missing == nil) assert.True(t, possible == nil) missing, possible, err = db.RevDiff("doc1", []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "3-foo"}) assertNoError(t, err, "RevDiff failed") assert.DeepEquals(t, missing, []string{"3-foo"}) assert.DeepEquals(t, possible, []string{"2-488724414d0ed6b398d6d2aeb228d797"}) missing, possible, err = db.RevDiff("nosuchdoc", []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "3-foo"}) assertNoError(t, err, "RevDiff failed") assert.DeepEquals(t, missing, []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "3-foo"}) assert.True(t, possible == nil) // Test PutExistingRev: log.Printf("Check PutExistingRev...") body["_rev"] = "4-four" body["key1"] = "fourth value" body["key2"] = int64(4444) history := []string{"4-four", "3-three", "2-488724414d0ed6b398d6d2aeb228d797", "1-cb0c9a22be0e5a1b01084ec019defa81"} err = db.PutExistingRev("doc1", body, history) assertNoError(t, err, "PutExistingRev failed") // Retrieve the document: log.Printf("Check Get...") gotbody, err = db.Get("doc1") assertNoError(t, err, "Couldn't get document") assert.DeepEquals(t, gotbody, body) // Compact and check how many obsolete revs were deleted: revsDeleted, err := db.Compact() assertNoError(t, err, "Compact failed") assert.Equals(t, revsDeleted, 2) }
// If the error parameter is non-nil, sets the response status code appropriately and // writes a CouchDB-style JSON description to the body. func (h *handler) writeError(err error) { if err != nil { status, message := base.ErrorAsHTTPStatus(err) h.writeStatus(status, message) } }
func TestAttachments(t *testing.T) { context, err := NewDatabaseContext("db", gTestBucket) assertNoError(t, err, "Couldn't create context for database 'db'") db, err := CreateDatabase(context) assertNoError(t, err, "Couldn't create database 'db'") defer func() { err = db.Delete() status, _ := base.ErrorAsHTTPStatus(err) if status != 200 && status != 404 { assertNoError(t, err, "Couldn't delete database 'db'") } }() // Test creating & updating a document: log.Printf("Create rev 1...") rev1input := `{"_attachments": {"hello.txt": {"data":"aGVsbG8gd29ybGQ="}, "bye.txt": {"data":"Z29vZGJ5ZSBjcnVlbCB3b3JsZA=="}}}` var body Body json.Unmarshal([]byte(rev1input), &body) revid, err := db.Put("doc1", unjson(rev1input)) assertNoError(t, err, "Couldn't create document") log.Printf("Retrieve doc...") rev1output := `{"_attachments":{"bye.txt":{"data":"Z29vZGJ5ZSBjcnVlbCB3b3JsZA==","digest":"sha1-l+N7VpXGnoxMm8xfvtWPbz2YvDc=","length":19,"revpos":1},"hello.txt":{"data":"aGVsbG8gd29ybGQ=","digest":"sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=","length":11,"revpos":1}},"_id":"doc1","_rev":"1-54f3a105fb903018c160712ffddb74dc"}` gotbody, err := db.GetRev("doc1", "", false, []string{}) assertNoError(t, err, "Couldn't get document") assert.Equals(t, tojson(gotbody), rev1output) log.Printf("Create rev 2...") rev2str := `{"_attachments": {"hello.txt": {}, "bye.txt": {"data": "YnllLXlh"}}}` var body2 Body json.Unmarshal([]byte(rev2str), &body2) body2["_rev"] = revid revid, err = db.Put("doc1", body2) assertNoError(t, err, "Couldn't update document") assert.Equals(t, revid, "2-08b42c51334c0469bd060e6d9e6d797b") log.Printf("Retrieve doc...") rev2output := `{"_attachments":{"bye.txt":{"data":"YnllLXlh","digest":"sha1-gwwPApfQR9bzBKpqoEYwFmKp98A=","length":6,"revpos":2},"hello.txt":{"data":"aGVsbG8gd29ybGQ=","digest":"sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=","length":11,"revpos":1}},"_id":"doc1","_rev":"2-08b42c51334c0469bd060e6d9e6d797b"}` gotbody, err = db.GetRev("doc1", "", false, []string{}) assertNoError(t, err, "Couldn't get document") assert.Equals(t, tojson(gotbody), rev2output) log.Printf("Retrieve doc with atts_since...") rev2Aoutput := `{"_attachments":{"bye.txt":{"data":"YnllLXlh","digest":"sha1-gwwPApfQR9bzBKpqoEYwFmKp98A=","length":6,"revpos":2},"hello.txt":{"digest":"sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=","length":11,"revpos":1,"stub":true}},"_id":"doc1","_rev":"2-08b42c51334c0469bd060e6d9e6d797b"}` gotbody, err = db.GetRev("doc1", "", false, []string{"1-54f3a105fb903018c160712ffddb74dc", "1-foo", "993-bar"}) assertNoError(t, err, "Couldn't get document") assert.Equals(t, tojson(gotbody), rev2Aoutput) log.Printf("Create rev 3...") rev3str := `{"_attachments": {"bye.txt": {}}}` var body3 Body json.Unmarshal([]byte(rev3str), &body3) body3["_rev"] = revid revid, err = db.Put("doc1", body3) assertNoError(t, err, "Couldn't update document") assert.Equals(t, revid, "3-252b9fa1f306930bffc07e7d75b77faf") log.Printf("Retrieve doc...") rev3output := `{"_attachments":{"bye.txt":{"data":"YnllLXlh","digest":"sha1-gwwPApfQR9bzBKpqoEYwFmKp98A=","length":6,"revpos":2}},"_id":"doc1","_rev":"3-252b9fa1f306930bffc07e7d75b77faf"}` gotbody, err = db.GetRev("doc1", "", false, []string{}) assertNoError(t, err, "Couldn't get document") assert.Equals(t, tojson(gotbody), rev3output) }
func TestDatabase(t *testing.T) { db, err := CreateDatabase(&DatabaseContext{Name: "db", Bucket: gTestBucket}) assertNoError(t, err, "Couldn't create database 'db'") defer func() { err = db.Delete() status, _ := base.ErrorAsHTTPStatus(err) if status != 200 && status != 404 { assertNoError(t, err, "Couldn't delete database 'db'") } }() // Test creating & updating a document: log.Printf("Create rev 1...") body := Body{"key1": "value1", "key2": 1234} revid, err := db.Put("doc1", body) assertNoError(t, err, "Couldn't create document") assert.Equals(t, revid, body["_rev"]) assert.Equals(t, revid, "1-cb0c9a22be0e5a1b01084ec019defa81") log.Printf("Create rev 2...") body["key1"] = "new value" body["key2"] = float64(4321) // otherwise the DeepEquals call below fails revid, err = db.Put("doc1", body) body["_id"] = "doc1" assertNoError(t, err, "Couldn't update document") assert.Equals(t, revid, body["_rev"]) assert.Equals(t, revid, "2-488724414d0ed6b398d6d2aeb228d797") // Retrieve the document: log.Printf("Retrieve doc...") gotbody, err := db.Get("doc1") assertNoError(t, err, "Couldn't get document") assert.DeepEquals(t, gotbody, body) gotbody, err = db.GetRev("doc1", revid, false, nil) assertNoError(t, err, "Couldn't get document with rev") assert.DeepEquals(t, gotbody, body) gotbody, err = db.GetRev("doc1", "bogusrev", false, nil) status, _ := base.ErrorAsHTTPStatus(err) assert.Equals(t, status, 404) // Test the _revisions property: log.Printf("Check _revisions...") gotbody, err = db.GetRev("doc1", revid, true, nil) revisions := gotbody["_revisions"].(Body) assert.Equals(t, revisions["start"], 2) assert.DeepEquals(t, revisions["ids"], []string{"488724414d0ed6b398d6d2aeb228d797", "cb0c9a22be0e5a1b01084ec019defa81"}) // Test RevDiff: log.Printf("Check RevDiff...") missing, possible, err := db.RevDiff("doc1", []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "2-488724414d0ed6b398d6d2aeb228d797"}) assertNoError(t, err, "RevDiff failed") assert.True(t, missing == nil) assert.True(t, possible == nil) missing, possible, err = db.RevDiff("doc1", []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "3-foo"}) assertNoError(t, err, "RevDiff failed") assert.DeepEquals(t, missing, []string{"3-foo"}) assert.DeepEquals(t, possible, []string{"2-488724414d0ed6b398d6d2aeb228d797"}) missing, possible, err = db.RevDiff("nosuchdoc", []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "3-foo"}) assertNoError(t, err, "RevDiff failed") assert.DeepEquals(t, missing, []string{"1-cb0c9a22be0e5a1b01084ec019defa81", "3-foo"}) assert.True(t, possible == nil) // Test PutExistingRev: log.Printf("Check PutExistingRev...") body["_rev"] = "4-four" body["key1"] = "fourth value" body["key2"] = float64(4444) history := []string{"4-four", "3-three", "2-488724414d0ed6b398d6d2aeb228d797", "1-cb0c9a22be0e5a1b01084ec019defa81"} err = db.PutExistingRev("doc1", body, history) assertNoError(t, err, "PutExistingRev failed") // Retrieve the document: log.Printf("Check Get...") gotbody, err = db.Get("doc1") assertNoError(t, err, "Couldn't get document") assert.DeepEquals(t, gotbody, body) }
// HTTP handler for a POST to _bulk_get func (h *handler) handleBulkGet() error { includeRevs := h.getBoolQuery("revs") includeAttachments := h.getBoolQuery("attachments") body, err := h.readJSON() if err != nil { return err } h.setHeader("Content-Type", "application/json") h.response.Write([]byte(`[`)) for i, item := range body["docs"].([]interface{}) { doc := item.(map[string]interface{}) docid, _ := doc["id"].(string) revid := "" revok := true if doc["rev"] != nil { revid, revok = doc["rev"].(string) } if docid == "" || !revok { return &base.HTTPError{http.StatusBadRequest, "Invalid doc/rev ID"} } var attsSince []string = nil if includeAttachments { if doc["atts_since"] != nil { raw, ok := doc["atts_since"].([]interface{}) if ok { attsSince = make([]string, len(raw)) for i := 0; i < len(raw); i++ { attsSince[i], ok = raw[i].(string) if !ok { break } } } if !ok { return &base.HTTPError{http.StatusBadRequest, "Invalid atts_since"} } } else { attsSince = []string{} } } body, err := h.db.GetRev(docid, revid, includeRevs, attsSince) if err != nil { status, reason := base.ErrorAsHTTPStatus(err) errStr := base.CouchHTTPErrorName(status) body = db.Body{"id": docid, "error": errStr, "reason": reason, "status": status} if revid != "" { body["rev"] = revid } } if i > 0 { h.response.Write([]byte(",\n")) } h.addJSON(body) } h.response.Write([]byte(`]`)) return nil }
// HTTP handler for a POST to _bulk_get // Request looks like POST /db/_bulk_get?revs=___&attachments=___ // where the boolean ?revs parameter adds a revision history to each doc // and the boolean ?attachments parameter includes attachment bodies. // The body of the request is JSON and looks like: // { // "docs": [ // {"id": "docid", "rev": "revid", "atts_since": [12,...]}, ... // ] // } func (h *handler) handleBulkGet() error { includeRevs := h.getBoolQuery("revs") includeAttachments := h.getBoolQuery("attachments") canCompress := strings.Contains(h.rq.Header.Get("X-Accept-Part-Encoding"), "gzip") body, err := h.readJSON() if err != nil { return err } err = h.writeMultipart(func(writer *multipart.Writer) error { for _, item := range body["docs"].([]interface{}) { var body db.Body var attsSince []string var err error doc := item.(map[string]interface{}) docid, _ := doc["id"].(string) revid := "" revok := true if doc["rev"] != nil { revid, revok = doc["rev"].(string) } if docid == "" || !revok { err = base.HTTPErrorf(http.StatusBadRequest, "Invalid doc/rev ID in _bulk_get") } else { if includeAttachments { if doc["atts_since"] != nil { raw, ok := doc["atts_since"].([]interface{}) if ok { attsSince = make([]string, len(raw)) for i := 0; i < len(raw); i++ { attsSince[i], ok = raw[i].(string) if !ok { break } } } if !ok { err = base.HTTPErrorf(http.StatusBadRequest, "Invalid atts_since") } } else { attsSince = []string{} } } } if err == nil { body, err = h.db.GetRev(docid, revid, includeRevs, attsSince) } if err != nil { // Report error in the response for this doc: status, reason := base.ErrorAsHTTPStatus(err) errStr := base.CouchHTTPErrorName(status) body = db.Body{"id": docid, "error": errStr, "reason": reason, "status": status} if revid != "" { body["rev"] = revid } } h.db.WriteRevisionAsPart(body, err != nil, canCompress, writer) } return nil }) return err }
func isMissingDocError(err error) bool { httpstatus, _ := base.ErrorAsHTTPStatus(err) return httpstatus == 404 }
// HTTP handler for _all_docs func (h *handler) handleAllDocs() error { // http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API includeDocs := h.getBoolQuery("include_docs") includeChannels := h.getBoolQuery("channels") includeAccess := h.getBoolQuery("access") && h.user == nil includeRevs := h.getBoolQuery("revs") includeSeqs := h.getBoolQuery("update_seq") // Get the doc IDs if this is a POST request: var explicitDocIDs []string if h.rq.Method == "POST" { input, err := h.readJSON() if err == nil { keys, ok := input["keys"].([]interface{}) explicitDocIDs = make([]string, len(keys)) for i := 0; i < len(keys); i++ { if explicitDocIDs[i], ok = keys[i].(string); !ok { break } } if !ok { err = base.HTTPErrorf(http.StatusBadRequest, "Bad/missing keys") } } } // Get the set of channels the user has access to; nil if user is admin or has access to "*" var availableChannels channels.TimedSet if h.user != nil { availableChannels = h.user.InheritedChannels() if availableChannels == nil { panic("no channels for user?") } if availableChannels.Contains("*") { availableChannels = nil } } // Subroutines that filter a channel list down to the ones that the user has access to: filterChannels := func(channels []string) []string { if availableChannels == nil { return channels } dst := 0 for _, ch := range channels { if availableChannels.Contains(ch) { channels[dst] = ch dst++ } } if dst == 0 { return nil } return channels[0:dst] } filterChannelSet := func(channelMap channels.ChannelMap) []string { var result []string if availableChannels == nil { result = []string{} } for ch, _ := range channelMap { if availableChannels == nil || availableChannels.Contains(ch) { result = append(result, ch) } } return result } type allDocsRowValue struct { Rev string `json:"rev"` Channels []string `json:"channels,omitempty"` Access map[string]base.Set `json:"access,omitempty"` // for admins only } type allDocsRow struct { Key string `json:"key"` ID string `json:"id,omitempty"` Value *allDocsRowValue `json:"value,omitempty"` Doc db.Body `json:"doc,omitempty"` UpdateSeq uint64 `json:"update_seq,omitempty"` Error string `json:"error,omitempty"` Status int `json:"status,omitempty"` } // Subroutine that creates a response row for a document: totalRows := 0 createRow := func(doc db.IDAndRev, channels []string) *allDocsRow { row := &allDocsRow{Key: doc.DocID} value := allDocsRowValue{} // Filter channels to ones available to user, and bail out if inaccessible: if explicitDocIDs == nil { if channels = filterChannels(channels); channels == nil { return nil // silently skip this doc } } if explicitDocIDs != nil || includeDocs || includeAccess { // Fetch the document body and other metadata that lives with it: body, channelSet, access, roleAccess, err := h.db.GetRevAndChannels(doc.DocID, doc.RevID, includeRevs) if err != nil { row.Status, _ = base.ErrorAsHTTPStatus(err) return row } else if body["_removed"] != nil { row.Status = http.StatusForbidden return row } if explicitDocIDs != nil { if channels = filterChannelSet(channelSet); channels == nil { row.Status = http.StatusForbidden return row } doc.RevID = body["_rev"].(string) } if includeDocs { row.Doc = body } if includeAccess && (access != nil || roleAccess != nil) { value.Access = map[string]base.Set{} for userName, channels := range access { value.Access[userName] = channels.AsSet() } for roleName, channels := range roleAccess { value.Access["role:"+roleName] = channels.AsSet() } } } row.Value = &value row.ID = doc.DocID value.Rev = doc.RevID if includeSeqs { row.UpdateSeq = doc.Sequence } if includeChannels { row.Value.Channels = channels } return row } // Subroutine that writes a response entry for a document: writeDoc := func(doc db.IDAndRev, channels []string) error { row := createRow(doc, channels) if row != nil { if row.Status >= 300 { row.Error = base.CouchHTTPErrorName(row.Status) } if totalRows > 0 { h.response.Write([]byte(",")) } totalRows++ h.addJSON(row) } return nil } // Now it's time to actually write the response! lastSeq, _ := h.db.LastSequence() h.setHeader("Content-Type", "application/json") h.response.Write([]byte(`{"rows":[` + "\n")) if explicitDocIDs != nil { for _, docID := range explicitDocIDs { writeDoc(db.IDAndRev{DocID: docID, RevID: "", Sequence: 0}, nil) } } else { if err := h.db.ForEachDocID(writeDoc); err != nil { return err } } h.response.Write([]byte(fmt.Sprintf("],\n"+`"total_rows":%d,"update_seq":%d}`, totalRows, lastSeq))) return nil }