package pub import ( "context" "encoding/json" "errors" "fmt" "net/http" "github.com/superseriousbusiness/activity/streams" ) var ErrNotFound = errors.New("go-fed/activity: ActivityStreams data not found") // HandlerFunc determines whether an incoming HTTP request is an ActivityStreams // GET request, and if so attempts to serve ActivityStreams data. // // If an error is returned, then the calling function is responsible for writing // to the ResponseWriter as part of error handling. // // If 'isASRequest' is false and there is no error, then the calling function // may continue processing the request, and the HandlerFunc will not have // written anything to the ResponseWriter. For example, a webpage may be served // instead. // // If 'isASRequest' is true and there is no error, then the HandlerFunc // successfully served the request and wrote to the ResponseWriter. // // Callers are responsible for authorized access to this resource. type HandlerFunc func(c context.Context, w http.ResponseWriter, r *http.Request) (isASRequest bool, err error) // NewActivityStreamsHandler creates a HandlerFunc to serve ActivityStreams // requests which are coming from other clients or servers that wish to obtain // an ActivityStreams representation of data. // // Strips retrieved ActivityStreams values of sensitive fields ('bto' and 'bcc') // before responding with them. Sets the appropriate HTTP status code for // Tombstone Activities as well. // // Defaults to supporting content to be retrieved by HTTPS only. func NewActivityStreamsHandler(db Database, clock Clock) HandlerFunc { return NewActivityStreamsHandlerScheme(db, clock, "https") } // NewActivityStreamsHandlerScheme creates a HandlerFunc to serve // ActivityStreams requests which are coming from other clients or servers that // wish to obtain an ActivityStreams representation of data provided by the // specified protocol scheme. // // Strips retrieved ActivityStreams values of sensitive fields ('bto' and 'bcc') // before responding with them. Sets the appropriate HTTP status code for // Tombstone Activities as well. // // Specifying the "scheme" allows for retrieving ActivityStreams content with // identifiers such as HTTP, HTTPS, or other protocol schemes. // // Returns ErrNotFound when the database does not retrieve any data and no // errors occurred during retrieval. func NewActivityStreamsHandlerScheme(db Database, clock Clock, scheme string) HandlerFunc { return func(c context.Context, w http.ResponseWriter, r *http.Request) (isASRequest bool, err error) { // Do nothing if it is not an ActivityPub GET request if !isActivityPubGet(r) { return } isASRequest = true id := requestId(r, scheme) var unlock func() // Lock and obtain a copy of the requested ActivityStreams value unlock, err = db.Lock(c, id) if err != nil { return } // WARNING: Unlock not deferred t, err := db.Get(c, id) unlock() // unlock even on error if err != nil { return } // Unlock must have been called by this point and in every // branch above if t == nil { err = ErrNotFound return } // Remove sensitive fields. clearSensitiveFields(t) // Serialize the fetched value. m, err := streams.Serialize(t) if err != nil { return } raw, err := json.Marshal(m) if err != nil { return } // Construct the response. addResponseHeaders(w.Header(), clock, raw) // Write the response. if streams.IsOrExtendsActivityStreamsTombstone(t) { w.WriteHeader(http.StatusGone) } else { w.WriteHeader(http.StatusOK) } n, err := w.Write(raw) if err != nil { return } else if n != len(raw) { err = fmt.Errorf("only wrote %d of %d bytes", n, len(raw)) return } return } }