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  }