github.com/readium/readium-lcp-server@v0.0.0-20240509124024-799e77a0bbd6/lcpserver/server/server.go (about) 1 // Copyright (c) 2016 Readium Foundation 2 // 3 // Redistribution and use in source and binary forms, with or without modification, 4 // are permitted provided that the following conditions are met: 5 // 6 // 1. Redistributions of source code must retain the above copyright notice, this 7 // list of conditions and the following disclaimer. 8 // 2. Redistributions in binary form must reproduce the above copyright notice, 9 // this list of conditions and the following disclaimer in the documentation and/or 10 // other materials provided with the distribution. 11 // 3. Neither the name of the organization nor the names of its contributors may be 12 // used to endorse or promote products derived from this software without specific 13 // prior written permission 14 // 15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 26 package lcpserver 27 28 import ( 29 "crypto/tls" 30 "net/http" 31 "time" 32 33 auth "github.com/abbot/go-http-auth" 34 "github.com/gorilla/mux" 35 36 "github.com/readium/readium-lcp-server/api" 37 "github.com/readium/readium-lcp-server/config" 38 "github.com/readium/readium-lcp-server/index" 39 apilcp "github.com/readium/readium-lcp-server/lcpserver/api" 40 "github.com/readium/readium-lcp-server/license" 41 "github.com/readium/readium-lcp-server/pack" 42 "github.com/readium/readium-lcp-server/storage" 43 ) 44 45 type Server struct { 46 http.Server 47 readonly bool 48 idx *index.Index 49 st *storage.Store 50 lst *license.Store 51 cert *tls.Certificate 52 source pack.ManualSource 53 testMode bool 54 } 55 56 func (s *Server) Store() storage.Store { 57 return *s.st 58 } 59 60 func (s *Server) Index() index.Index { 61 return *s.idx 62 } 63 64 func (s *Server) Licenses() license.Store { 65 return *s.lst 66 } 67 68 func (s *Server) Certificate() *tls.Certificate { 69 return s.cert 70 } 71 72 func (s *Server) Source() *pack.ManualSource { 73 return &s.source 74 } 75 76 func (s *Server) TestMode() bool { 77 return s.testMode 78 } 79 80 func New(bindAddr string, readonly bool, idx *index.Index, st *storage.Store, lst *license.Store, cert *tls.Certificate, packager *pack.Packager, basicAuth *auth.BasicAuth) *Server { 81 82 sr := api.CreateServerRouter("") 83 84 s := &Server{ 85 Server: http.Server{ 86 Handler: sr.N, 87 Addr: bindAddr, 88 WriteTimeout: 240 * time.Second, 89 ReadTimeout: 15 * time.Second, 90 MaxHeaderBytes: 1 << 20, 91 }, 92 readonly: readonly, 93 idx: idx, 94 st: st, 95 lst: lst, 96 cert: cert, 97 source: pack.ManualSource{}, 98 } 99 100 // Route.PathPrefix: http://www.gorillatoolkit.org/pkg/mux#Route.PathPrefix 101 // Route.Subrouter: http://www.gorillatoolkit.org/pkg/mux#Route.Subrouter 102 // Router.StrictSlash: http://www.gorillatoolkit.org/pkg/mux#Router.StrictSlash 103 104 // Serve static resources from a configurable directory. 105 // This is used when lcpencrypt sends encrypted resources and cover images to an fs storage, 106 // and we want this http server to provide such resources to the outside world (e.g. PubStore). 107 resourceDir := config.Config.LcpServer.Resources 108 sr.R.PathPrefix("/resources/").Handler(http.StripPrefix("/resources/", http.FileServer(http.Dir(resourceDir)))) 109 110 // Methods related to encrypted content 111 112 contentRoutesPathPrefix := "/contents" 113 contentRoutes := sr.R.PathPrefix(contentRoutesPathPrefix).Subrouter().StrictSlash(false) 114 115 s.handleFunc(sr.R, contentRoutesPathPrefix, apilcp.ListContents).Methods("GET") 116 117 // get encrypted content by content id (a uuid) 118 s.handleFunc(contentRoutes, "/{content_id}", apilcp.GetContent).Methods("GET") 119 // get all licenses associated with a given content 120 s.handlePrivateFunc(contentRoutes, "/{content_id}/licenses", apilcp.ListLicensesForContent, basicAuth).Methods("GET") 121 122 if !readonly { 123 // create a publication 124 s.handlePrivateFunc(contentRoutes, "/{content_id}", apilcp.AddContent, basicAuth).Methods("PUT") 125 // delete a publication 126 s.handlePrivateFunc(contentRoutes, "/{content_id}", apilcp.DeleteContent, basicAuth).Methods("DELETE") 127 // generate a license for given content 128 s.handlePrivateFunc(contentRoutes, "/{content_id}/license", apilcp.GenerateLicense, basicAuth).Methods("POST") 129 // deprecated, from a typo in the lcp server spec 130 s.handlePrivateFunc(contentRoutes, "/{content_id}/licenses", apilcp.GenerateLicense, basicAuth).Methods("POST") 131 // generate a protected publication 132 s.handlePrivateFunc(contentRoutes, "/{content_id}/publication", apilcp.GenerateProtectedPublication, basicAuth).Methods("POST") 133 // deprecated, from a typo in the lcp server spec 134 s.handlePrivateFunc(contentRoutes, "/{content_id}/publications", apilcp.GenerateProtectedPublication, basicAuth).Methods("POST") 135 } 136 137 // Methods related to licenses 138 139 licenseRoutesPathPrefix := "/licenses" 140 licenseRoutes := sr.R.PathPrefix(licenseRoutesPathPrefix).Subrouter().StrictSlash(false) 141 142 // this is a test route 143 s.handleFunc(licenseRoutes, "/test/{license_id}", apilcp.GetTestLicense).Methods("GET") 144 145 s.handlePrivateFunc(sr.R, licenseRoutesPathPrefix, apilcp.ListLicenses, basicAuth).Methods("GET") 146 // get a license 147 s.handlePrivateFunc(licenseRoutes, "/{license_id}", apilcp.GetLicense, basicAuth).Methods("GET") 148 s.handlePrivateFunc(licenseRoutes, "/{license_id}", apilcp.GetLicense, basicAuth).Methods("POST") 149 // get a protected publication via a license id 150 s.handlePrivateFunc(licenseRoutes, "/{license_id}/publication", apilcp.GetProtectedPublication, basicAuth).Methods("POST") 151 if !readonly { 152 // update a license 153 s.handlePrivateFunc(licenseRoutes, "/{license_id}", apilcp.UpdateLicense, basicAuth).Methods("PATCH") 154 } 155 156 s.source.Feed(packager.Incoming) 157 return s 158 } 159 160 type HandlerFunc func(w http.ResponseWriter, r *http.Request, s apilcp.Server) 161 162 func (s *Server) handleFunc(router *mux.Router, route string, fn HandlerFunc) *mux.Route { 163 return router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) { 164 fn(w, r, s) 165 }) 166 } 167 168 type HandlerPrivateFunc func(w http.ResponseWriter, r *auth.AuthenticatedRequest, s apilcp.Server) 169 170 func (s *Server) handlePrivateFunc(router *mux.Router, route string, fn HandlerFunc, authenticator *auth.BasicAuth) *mux.Route { 171 return router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) { 172 if api.CheckAuth(authenticator, w, r) { 173 fn(w, r, s) 174 } 175 }) 176 }