diff --git a/.gitignore b/.gitignore
index 84c14060..3ce6bd97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,23 @@ readium-lcp-server
lcpencrypt/lcpencrypt
files/
*.sqlite*
+config.yaml
+lcpserver/manage/config.js
+.vscode/launch.json
+debug
+*.exe
+*.yaml
+**/manage/config.js
+frontend/manage/node_modules/*
+frontend/manage/dist/*
+frontend/manage/js/*
+frontend/manage/js/components/*
+*.map
+*.htpasswd
+lcpserver/test.sqllite
+.DS_Store
+.vscode
+lcpencrypt/.DS_Store
+lcpserver/.DS_Store
+npm-debug.log
+manage/config.js
\ No newline at end of file
diff --git a/README.md b/README.md
index 8576c8a2..e058dc58 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
Readium LCP Server
-========
-
-This server allows you to both encrypt EPUBs as well as deliver licenses in accordance with the Readium LCP specification.
+==================
Requirements
============
@@ -9,45 +7,182 @@ Requirements
No binaries are currently pre-built, so you need to get a working Golang installation. Please refer to the official documentation for
installation procedures at https://golang.org/.
-In order to keep the content keys for each encrypted EPUB, the server requires a SQL Database. The server currently includes drivers
-for SQLite (the default option, and should be fine for small to medium installations) as well as MySQL and Postgres.
+In order to keep the content keys for each encrypted EPUB, the server requires an SQL Database. The server currently includes drivers
+for SQLite (the default option, which should be fine for small to medium installations) as well as MySQL and Postgres.
-If you wish to use the external licenses, where a client gets a simple json file that contains instructions on how to fetch the encrypted EPUBS,
+If you wish to use the external licenses, where a client gets a simple json file that contains instructions on how to fetch the encrypted EPUB file,
a publicly accessible folder must be made available for the server to store the file.
-You must obtain a X.509 certificate through the Readium Foundation in order for your licenses to be accepted by the Reading Systems.
+You must obtain a X.509 certificate through EDRLab in order for your licenses to be accepted by Readium LCP compliant Reading Systems.
-Install
-======
+Executables
+===========
+The server software is composed of three independant parts:
-Assuming a working Go installation,
+## [lcpencrypt]
-go get github.com/readium/readium-lcp-server
+A command line utility for EPUB content encryption. This utility can be included in any processing pipeline.
+
+* takes one unprotected EPUB 3 file as input and generates an encrypted file as output.
+* notifies the License server of the generation of an encrypted file.
+
+## [lcpserver]
+
+A License server, which implements Readium Licensed Content Protection 1.0.
-Usage
-=====
+Private functionalities (authentication needed):
+* Store the data resulting from an external encryption
+* Generate a license
+* Generate a protected publication
+* Update the rights associated with a license
+* Get a set of licenses
+* Get a license
-*Please note that the LCP Server currently does not include any authentication. Make sure it is only available to your internal services or add an authenticating
-proxy in front of it*
-The server is controlled by a set of environment variables. Here are their descriptions and possible values:
+## [lsdserver]
-- CERT - Points to the certificate file (a .crt)
-- PRIVATE_KEY - Points to the private key (a .pem)
-- PORT - Where lcpserve will listen, by default 8989
-- HOST - The public hostname, defaults to `hostname`
-- READONLY - Readonly mode for demo purposes, no new file can be packaged
-- DB - the connection string to the database, by default sqlite3://file:lcpserve.sqlite?cache=shared&mode=rwc
+A License Status server, which implements Readium License Status Document 1.0.
-You can also use a YAML config file named config.yaml, which follows the structure presented in config.yaml.sample
+Public functionalities (accessible from the web):
+* Return a license status document
+* Process a device registration
+* Process a lending return
+* Process a lending renewal
+Private functionalities (authentication needed):
+* Create a license status document
+* Filter licenses
+* List all registered devices for a given licence
+* Revoke/cancel a license
-Once those are set, you can run the server by calling the following:
-$GOPATH/bin/readium-lcp-server
+Install
+=======
+
+Assuming a working Go installation, the following will install the three executables that constitute a complete Readium LCP Server.
+
+If you want to use the master branch:
+```sh
+// from the go workspace
+cd $GOPATH
+// get the different packages and their dependencies, then installs the packages
+go get github.com/readium/readium-lcp-server
+```
+
+If you want to use a feature/F branch:
+```sh
+// from the go workspace
+cd $GOPATH
+// create the project repository
+mkdir -p src/github.com/readium/readium-lcp-server
+// clone the repo, selecting the development branch
+git clone -b feature/F https://github.com/readium/readium-lcp-server.git src/github.com/readium/readium-lcp-server
+// move to the project repository
+cd src/github.com/readium/readium-lcp-server
+// get the different packages and their dependencies, then installs the packages (dot / triple dot pattern)
+go get ./...
+```
+
+You may prefer to install only some of the three executables.
+In such a case, the "go get" command should be called once for each package, e.g. for the lcpserver from the master branch:
+```sh
+// from the go workspace
+cd $GOPATH
+// get the different packages and their dependencies, then installs the packages
+go get github.com/readium/readium-lcp-server/lcpserver
+```
+
+Server Configuration
+====================
+
+The server is controlled by a yaml configuration file (e.g. "config.yaml").
+The License Server and License Status Server will search their configuration file in the bin directory by default;
+but the path to this file can be changed using the environment variable READIUM_LICENSE_CONFIG.
+The License Server and License Status Server may share the same configuration file (if they are both executed on the same server)
+or they can have their own configucation file. In the first case, the htpasswd file and database may also be shared.
+In the latter case, the License Server will have a "lcp" section and a "lsd_notify_auth" section;
+the License Status Server will have a "lsd" section and a "lcp_update_auth" section.
+
+"certificate": parameters related to the signature of the licenses
+- "cert": the provider certificate file (.pem or .crt). It will be inserted in the licenses and used by clients for checking the signature.
+- "private_key": the private key (.pem). It will be used for signing licenses.
+
+"lcp" (License Server) & "lsd" (License Status Server) sections have an identical structure:
+- "host": the public server hostname, `hostname` by default
+- "port": the listening port, `8989` by default
+- "public_base_url": the public base URL, combination of the host and port values on http by default
+- "database": the URI formatted connection string to the database, `sqlite3://file:lcpserve.sqlite?cache=shared&mode=rwc` by default
+- "auth_file": mandatory; the authentication file (an .htpasswd). Passwords must be encrypted using MD5.
+ The source example for creating password is http://www.htaccesstools.com/htpasswd-generator/.
+ The format of the file is:
+```sh
+ User1:$apr1$OMWGq53X$Qf17b.ezwEM947Vrr/oTh0
+ User2:$apr1$lldfYQA5$8fVeTVyKsiPeqcBWrjBKMT
+```
+
+"lsd_notify_auth": authentication parameters used by the License Server for notifying the License Status Server
+of a license generation. The notification endpoint is configured in the "lsd" section.
+- "username": mandatory, authentication username
+- "password": mandatory, authentication password
+
+"lcp_update_auth": authentication parameters used by the License Status Server for updating a license via the License Server.
+The notification endpoint is configured in the "lcp" section.
+- "username": mandatory, authentication username
+- "password": mandatory, authentication password
+
+"storage": parameters related to the storage of the protected publications.
+- "filesystem": parameters related to a file system storage
+ - "directory": absolute path to the directory in which the protected publications are stored.
+
+"license": parameters related to static information to be included in all licenses generated by the License Server
+- "links": links that will be included in all licenses. "hint" and "publication" links are required in a Readium LCP license.
+ If no such link exists in the partial license passed from the frontend when a new license his requested,
+ these link values will be inserted in the partial license.
+ If no value is present in the configuration file and no value is inserted in the partial license,
+ the License server will reply with a 500 Server Error at license creation.
+ - "hint": required; location where a Reading System can redirect a User looking for additional information about the User Passphrase.
+ - "publication": optional, templated URL;
+ location where the Publication associated with the License Document can be downloaded.
+ The publication identifier is inserted via the variable {publication_id}.
+ - "status": optional, templated URL; location of the Status Document associated with a License Document.
+ The license identifier is inserted via the variable {license_id}.
+
+NOTE: here is a license section snippet:
+```json
+license:
+ links:
+ hint: "http://www.edrlab.org/readiumlcp/static/hint.html"
+ publication: "http://www.edrlab.org/readiumlcp/files/{publication_id}"
+ status: "http://www.edrlab.org/readiumlcp/licenses/{license_id}/status"
+```
+
+"license_status": parameters related to the interactions implemented by the License Status server, if any
+- renting_days: number of days be the license ends.
+- renew: boolean; if `true`, rental renewal is possible.
+- renew_days: number of days added to the license if renewal is active.
+- return: boolean; if `true`, early return is possible.
+- register: boolean; if `true`, registering a device is possible.
+
+"localization": parameters related to the localization of the messages sent by the server
+- languages: array of supported localization languages
+- folder: point to localization file (a .json)
+- default_language: default language for localization
+
+NOTE: list files for localization (ex: 'en-US.json, de-DE.json') must match the array of supported localization languages
+
+"logging": parameters for logging results of API methods
+- log_directory: point to log file (a .log).
+- compliance_tests_mode_on: boolean; if `true`, logging is turned on.
+
+
+The following CBC / GCM configurable property is DISABLED, see https://github.com/readium/readium-lcp-server/issues/109
+"aes256_cbc_or_gcm": either "GCM" or "CBC" (which is the default value). This is used only for encrypting publication resources, not the content key, not the user key check, not the LCP license fields.
+
+
+Documentation
+============
+Detailed documentation can be found in the [Wiki pages](../../wiki) of the project.
-The server includes a basic web interface that can be reached at http://HOST:PORT/manage/. You can drag and drop EPUB files to encrypt them,
-as well as emit licenses for the currently encrypted EPUBs.
Contributing
============
diff --git a/api/common_server.go b/api/common_server.go
new file mode 100644
index 00000000..3d5bc320
--- /dev/null
+++ b/api/common_server.go
@@ -0,0 +1,166 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package api
+
+import (
+ "log"
+ "fmt"
+ "net/http"
+
+ "github.com/abbot/go-http-auth"
+ "github.com/gorilla/mux"
+ "github.com/urfave/negroni"
+ "github.com/jeffbmartinez/delay"
+ "github.com/technoweenie/grohl"
+ "github.com/rs/cors"
+
+ "github.com/readium/readium-lcp-server/problem"
+)
+
+const (
+ ContentType_LCP_JSON = "application/vnd.readium.lcp.license.1.0+json"
+ ContentType_LSD_JSON = "application/vnd.readium.license.status.v1.0+json"
+
+ ContentType_JSON = "application/json"
+
+ ContentType_FORM_URL_ENCODED = "application/x-www-form-urlencoded"
+)
+
+type ServerRouter struct {
+ R *mux.Router
+ N *negroni.Negroni
+}
+
+func CreateServerRouter(tplPath string) ServerRouter {
+
+ r := mux.NewRouter()
+
+ r.NotFoundHandler = http.HandlerFunc(problem.NotFoundHandler) //handle all other requests 404
+
+ // this demonstrates a panic report
+ r.HandleFunc("/panic", func(w http.ResponseWriter, req *http.Request) {
+ panic("just testing. no worries.")
+ })
+
+ //n := negroni.Classic() == negroni.New(negroni.NewRecovery(), negroni.NewLogger(), negroni.NewStatic(...))
+ n := negroni.New()
+
+ // HTTP client can emit requests with custom header:
+ //X-Add-Delay: 300ms
+ //X-Add-Delay: 2.5s
+ n.Use(delay.Middleware{})
+
+ // possibly useful middlewares:
+ // https://github.com/jeffbmartinez/delay
+
+ //https://github.com/urfave/negroni#recovery
+ recovery := negroni.NewRecovery()
+ recovery.PrintStack = true
+ recovery.ErrorHandlerFunc = problem.PanicReport
+ n.Use(recovery)
+
+ //https://github.com/urfave/negroni#logger
+ n.Use(negroni.NewLogger())
+
+ n.Use(negroni.HandlerFunc(ExtraLogger))
+
+ if tplPath != "" {
+ //https://github.com/urfave/negroni#static
+ n.Use(negroni.NewStatic(http.Dir(tplPath)))
+ }
+
+ n.Use(negroni.HandlerFunc(CORSHeaders))
+ // Does not insert CORS headers as intended, depends on Origin check in the HTTP request...we want the same headers, always.
+ // IMPORT "github.com/rs/cors"
+ // //https://github.com/rs/cors#parameters
+ c := cors.New(cors.Options{
+ AllowedOrigins: []string{"*"},
+ AllowedMethods: []string{"PATCH", "HEAD", "POST", "GET", "OPTIONS", "PUT", "DELETE"},
+ AllowedHeaders: []string{"Range", "Content-Type", "Origin", "X-Requested-With", "Accept", "Accept-Language", "Content-Language", "Authorization"},
+ Debug: true,
+ })
+ n.Use(c)
+
+ n.UseHandler(r)
+
+ sr := ServerRouter{
+ R: r,
+ N: n,
+ }
+
+ return sr
+}
+
+func ExtraLogger(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+
+ log.Print(" << -------------------")
+
+ fmt.Printf("%s => %s (%s)\n", r.RemoteAddr, r.URL.String(), r.RequestURI)
+
+ grohl.Log(grohl.Data{"method": r.Method, "path": r.URL.Path, "query": r.URL.RawQuery})
+
+ log.Printf("REQUEST headers: %#v", r.Header)
+
+ // before
+ next(rw, r)
+ // after
+
+ contentType := rw.Header().Get("Content-Type");
+ if contentType == problem.ContentType_PROBLEM_JSON {
+ log.Print("^^^^ " + problem.ContentType_PROBLEM_JSON + " ^^^^")
+ }
+
+ log.Printf("RESPONSE headers: %#v", rw.Header())
+
+ log.Print(" >> -------------------")
+}
+
+func CORSHeaders(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+
+ grohl.Log(grohl.Data{"CORS": "yes"})
+ rw.Header().Add("Access-Control-Allow-Methods", "PATCH, HEAD, POST, GET, OPTIONS, PUT, DELETE")
+ rw.Header().Add("Access-Control-Allow-Credentials", "true")
+ rw.Header().Add("Access-Control-Allow-Origin", "*")
+ rw.Header().Add("Access-Control-Allow-Headers", "Range, Content-Type, Origin, X-Requested-With, Accept, Accept-Language, Content-Language, Authorization")
+
+ // before
+ next(rw, r)
+ // after
+
+ // noop
+}
+
+func CheckAuth(authenticator *auth.BasicAuth, w http.ResponseWriter, r *http.Request) bool {
+ var username string
+ if username = authenticator.CheckAuth(r); username == "" {
+ grohl.Log(grohl.Data{"error": "Unauthorized", "method": r.Method, "path": r.URL.Path})
+ w.Header().Set("WWW-Authenticate", `Basic realm="`+authenticator.Realm+`"`)
+ problem.Error(w, r, problem.Problem{Detail: "User or password do not match!"}, http.StatusUnauthorized)
+ return false
+ }
+ grohl.Log(grohl.Data{"user": username})
+ return true
+}
diff --git a/api/struct.go b/api/struct.go
new file mode 100644
index 00000000..b348fd6a
--- /dev/null
+++ b/api/struct.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package api
+
+/*
+type Problem struct {
+ Type string `json:"type"`
+ //optionnal
+ Title string `json:"title,omitempty"`
+ Status int `json:"status,omitempty"` //if present = http response code
+ Detail string `json:"detail,omitempty"`
+ Instance string `json:"instance,omitempty"`
+ //Additional members
+}
+*/
diff --git a/authentication/md5password/makeMd5Password.go b/authentication/md5password/makeMd5Password.go
new file mode 100644
index 00000000..18ec12f2
--- /dev/null
+++ b/authentication/md5password/makeMd5Password.go
@@ -0,0 +1,59 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package main
+
+import (
+ "log"
+ "math/rand"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/abbot/go-http-auth"
+)
+
+func main() {
+
+ var salt []byte
+ var magic []byte
+ if len(os.Args) < 2 {
+ panic("need a password")
+ }
+ if len(os.Args) > 2 {
+ salt = []byte(os.Args[2])
+ } else {
+ r := rand.New(rand.NewSource(int64(time.Now().Unix())))
+ salt = []byte(strconv.Itoa(r.Int()))
+ }
+ if len(os.Args) > 3 {
+ magic = []byte("$" + string(os.Args[3]) + "$")
+ } else {
+ magic = []byte("$" + "$")
+ }
+
+ log.Println(string(auth.MD5Crypt([]byte(os.Args[1]), salt, magic)))
+
+}
diff --git a/authentication/passwords.htpasswd b/authentication/passwords.htpasswd
new file mode 100644
index 00000000..c4dbd4e7
--- /dev/null
+++ b/authentication/passwords.htpasswd
@@ -0,0 +1,3 @@
+Hanna:$apr1$OMWGq53X$Qf17b.ezwEM947Vrr/oTh0
+User:$apr1$lldfYQA5$8fVeTVyKsiPeqcBWrjBKM.
+Stefaan:$$8812069051407036158$00w3pIjfRqawvNxw9gzZs1
\ No newline at end of file
diff --git a/config.yaml.sample b/config.yaml.sample
deleted file mode 100644
index f3de7c03..00000000
--- a/config.yaml.sample
+++ /dev/null
@@ -1,7 +0,0 @@
-database: sqlite3://file:test.sqlite?cache=shared&mode=rwc
-certificate:
- cert: /home/banux/certs/lcp.crt
- private_key: /home/banux/certs/lcp.pem
-storage:
- filesystem:
- directory: /home/www/files
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 00000000..7ebf49b7
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,193 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package config
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+
+ "gopkg.in/yaml.v2"
+)
+
+type Configuration struct {
+ Certificate Certificate `yaml:"certificate"`
+ Storage Storage `yaml:"storage"`
+ License License `yaml:"license"`
+ LcpServer ServerInfo `yaml:"lcp"`
+ LsdServer LsdServerInfo `yaml:"lsd"`
+ FrontendServer FrontendServerInfo `yaml:"frontend"`
+ LsdNotifyAuth Auth `yaml:"lsd_notify_auth"`
+ LcpUpdateAuth Auth `yaml:"lcp_update_auth"`
+ LicenseStatus LicenseStatus `yaml:"license_status"`
+ Localization Localization `yaml:"localization"`
+ Logging Logging `yaml:"logging"`
+
+ // DISABLED, see https://github.com/readium/readium-lcp-server/issues/109
+ //AES256_CBC_OR_GCM string `yaml:"aes256_cbc_or_gcm,omitempty"`
+}
+
+type ServerInfo struct {
+ Host string `yaml:"host,omitempty"`
+ Port int `yaml:"port,omitempty"`
+ AuthFile string `yaml:"auth_file"`
+ ReadOnly bool `yaml:"readonly,omitempty"`
+ PublicBaseUrl string `yaml:"public_base_url,omitempty"`
+ Database string `yaml:"database,omitempty"`
+ Directory string `yaml:"directory,omitempty"`
+}
+
+type LsdServerInfo struct {
+ ServerInfo `yaml:",inline"`
+ LicenseLinkUrl string `yaml:"license_link_url,omitempty"`
+}
+
+type FrontendServerInfo struct {
+ ServerInfo `yaml:",inline"`
+ ProviderID string `yaml:"provider_id"`
+ MasterRepository string `yaml:"master_repository"`
+ EncryptedRepository string `yaml:"encrypted_repository"`
+}
+
+type Auth struct {
+ Username string `yaml:"username"`
+ Password string `yaml:"password"`
+}
+
+type Certificate struct {
+ Cert string `yaml:"cert"`
+ PrivateKey string `yaml:"private_key"`
+}
+
+type FileSystem struct {
+ Directory string `yaml:"directory"`
+}
+
+type Storage struct {
+ FileSystem FileSystem `yaml:"filesystem"`
+ AccessId string `yaml:"access_id"`
+ DisableSSL bool `yaml:"disable_ssl"`
+ PathStyle bool `yaml:"path_style"`
+ Mode string
+ Secret string
+ Endpoint string
+ Bucket string
+ Region string
+ Token string
+}
+
+type License struct {
+ Links map[string]string `yaml:"links"`
+}
+
+type LicenseStatus struct {
+ Renew bool `yaml:"renew"`
+ Register bool `yaml:"register"`
+ Return bool `yaml:"return"`
+ RentingDays int `yaml:"renting_days" "default 0"`
+ RenewDays int `yaml:"renew_days" "default 0"`
+}
+
+type Localization struct {
+ Languages []string `yaml:"languages"`
+ Folder string `yaml:"folder"`
+ DefaultLanguage string `yaml:"default_language"`
+}
+
+type Logging struct {
+ LogDirectory string `yaml:"log_directory"`
+ ComplianceTestsModeOn bool `yaml:"compliance_tests_mode_on"`
+}
+
+var Config Configuration
+
+func ReadConfig(configFileName string) {
+ filename, _ := filepath.Abs(configFileName)
+ yamlFile, err := ioutil.ReadFile(filename)
+
+ if err != nil {
+ panic("Can't read config file: " + configFileName)
+ }
+
+ err = yaml.Unmarshal(yamlFile, &Config)
+
+ if err != nil {
+ panic("Can't unmarshal config. " + configFileName + " -> " + err.Error())
+ }
+}
+
+func SetPublicUrls() error {
+ var lcpPublicBaseUrl, lsdPublicBaseUrl, frontendPublicBaseUrl, lcpHost, lsdHost, frontendHost string
+ var lcpPort, lsdPort, frontendPort int
+ var err error
+
+ if lcpHost = Config.LcpServer.Host; lcpHost == "" {
+ lcpHost, err = os.Hostname()
+ if err != nil {
+ return err
+ }
+ }
+
+ if lsdHost = Config.LsdServer.Host; lsdHost == "" {
+ lsdHost, err = os.Hostname()
+ if err != nil {
+ return err
+ }
+ }
+
+ if frontendHost = Config.FrontendServer.Host; frontendHost == "" {
+ frontendHost, err = os.Hostname()
+ if err != nil {
+ return err
+ }
+ }
+
+ if lcpPort = Config.LcpServer.Port; lcpPort == 0 {
+ lcpPort = 8989
+ }
+ if lsdPort = Config.LsdServer.Port; lsdPort == 0 {
+ lsdPort = 8990
+ }
+ if frontendPort = Config.FrontendServer.Port; frontendPort == 0 {
+ frontendPort = 80
+ }
+
+ if lcpPublicBaseUrl = Config.LcpServer.PublicBaseUrl; lcpPublicBaseUrl == "" {
+ lcpPublicBaseUrl = "http://" + lcpHost + ":" + strconv.Itoa(lcpPort)
+ Config.LcpServer.PublicBaseUrl = lcpPublicBaseUrl
+ }
+ if lsdPublicBaseUrl = Config.LsdServer.PublicBaseUrl; lsdPublicBaseUrl == "" {
+ lsdPublicBaseUrl = "http://" + lsdHost + ":" + strconv.Itoa(lsdPort)
+ Config.LsdServer.PublicBaseUrl = lsdPublicBaseUrl
+ }
+ if frontendPublicBaseUrl = Config.FrontendServer.PublicBaseUrl; frontendPublicBaseUrl == "" {
+ frontendPublicBaseUrl = "http://" + frontendHost + ":" + strconv.Itoa(frontendPort)
+ Config.FrontendServer.PublicBaseUrl = frontendPublicBaseUrl
+ }
+
+ return err
+}
diff --git a/crypto/aes_cbc.go b/crypto/aes_cbc.go
new file mode 100644
index 00000000..40dbdd92
--- /dev/null
+++ b/crypto/aes_cbc.go
@@ -0,0 +1,112 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package crypto
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "io"
+)
+
+type cbcEncrypter struct{}
+
+const (
+ aes256keyLength = 32 // 256 bits
+)
+
+func (e cbcEncrypter) Signature() string {
+ // W3C padding scheme, not PKCS#7 (see last parameter "insertPadLengthAll" [false] of PaddedReader constructor)
+ return "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
+}
+
+func (e cbcEncrypter) GenerateKey() (ContentKey, error) {
+ slice, err := GenerateKey(aes256keyLength)
+ return ContentKey(slice), err
+}
+
+func (e cbcEncrypter) Encrypt(key ContentKey, r io.Reader, w io.Writer) error {
+
+ r = PaddedReader(r, aes.BlockSize, false)
+
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return err
+ }
+
+ // generate the IV
+ iv := make([]byte, aes.BlockSize)
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return err
+ }
+
+ // write the IV first
+ if _, err = w.Write(iv); err != nil {
+ return err
+ }
+
+ mode := cipher.NewCBCEncrypter(block, iv)
+ buffer := make([]byte, aes.BlockSize)
+ for _, err = io.ReadFull(r, buffer); err == nil; _, err = io.ReadFull(r, buffer) {
+ mode.CryptBlocks(buffer, buffer)
+ _, wErr := w.Write(buffer)
+ if wErr != nil {
+ return wErr
+ }
+ }
+
+ if err == nil || err == io.EOF {
+ return nil
+ }
+
+ return err
+}
+
+func (c cbcEncrypter) Decrypt(key ContentKey, r io.Reader, w io.Writer) error {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return err
+ }
+
+ var buffer bytes.Buffer
+ io.Copy(&buffer, r)
+
+ buf := buffer.Bytes()
+ iv := buf[:aes.BlockSize]
+
+ mode := cipher.NewCBCDecrypter(block, iv)
+ mode.CryptBlocks(buf[aes.BlockSize:], buf[aes.BlockSize:])
+
+ padding := buf[len(buf)-1] // padding length valid for both PKCS#7 and W3C schemes
+ w.Write(buf[aes.BlockSize : len(buf)-int(padding)])
+
+ return nil
+}
+
+func NewAESCBCEncrypter() Encrypter {
+ return cbcEncrypter(struct{}{})
+}
diff --git a/crypto/encrypt_test.go b/crypto/aes_cbc_test.go
similarity index 50%
rename from crypto/encrypt_test.go
rename to crypto/aes_cbc_test.go
index ecf5bb01..08d40bac 100644
--- a/crypto/encrypt_test.go
+++ b/crypto/aes_cbc_test.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package crypto
import (
@@ -12,7 +37,9 @@ func TestSimpleEncrypt(t *testing.T) {
var output bytes.Buffer
var key [32]byte //not a safe key to have
- err := Encrypt(key[:], input, &output)
+ cbc := NewAESCBCEncrypter()
+
+ err := cbc.Encrypt(key[:], input, &output)
if err != nil {
t.Log(err)
@@ -31,7 +58,9 @@ func TestConsecutiveEncrypts(t *testing.T) {
var output bytes.Buffer
var key [32]byte //not a safe key to have
- err := Encrypt(key[:], input, &output)
+ cbc := NewAESCBCEncrypter()
+
+ err := cbc.Encrypt(key[:], input, &output)
if err != nil {
t.Log(err)
@@ -41,7 +70,7 @@ func TestConsecutiveEncrypts(t *testing.T) {
input = bytes.NewBufferString("1234")
var output2 bytes.Buffer
- err = Encrypt(key[:], input, &output2)
+ err = cbc.Encrypt(key[:], input, &output2)
if err != nil {
t.Log(err)
@@ -56,7 +85,10 @@ func TestConsecutiveEncrypts(t *testing.T) {
func TestFailingReaderForEncryption(t *testing.T) {
var output bytes.Buffer
var key [32]byte //not a safe key to have
- err := Encrypt(key[:], failingReader{}, &output)
+
+ cbc := NewAESCBCEncrypter()
+
+ err := cbc.Encrypt(key[:], failingReader{}, &output)
if err == nil {
t.Error("expected an error from the reader")
@@ -68,12 +100,14 @@ func TestDecrypt(t *testing.T) {
key := sha256.Sum256([]byte("password"))
var cipher bytes.Buffer
- err := Encrypt(key[:], clear, &cipher)
+ cbc := &cbcEncrypter{}
+ err := cbc.Encrypt(key[:], clear, &cipher)
if err != nil {
t.Fatal(err)
}
+
var res bytes.Buffer
- err = Decrypt(key[:], &cipher, &res)
+ err = cbc.Decrypt(key[:], &cipher, &res)
if err != nil {
t.Fatal(err)
}
@@ -103,4 +137,4 @@ func TestKeyWrap(t *testing.T) {
if !bytes.Equal(out, expected) {
t.Errorf("Expected %x, got %x", expected, out)
}
-}
+}
\ No newline at end of file
diff --git a/crypto/aes_gcm.go b/crypto/aes_gcm.go
new file mode 100644
index 00000000..a9866fef
--- /dev/null
+++ b/crypto/aes_gcm.go
@@ -0,0 +1,76 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package crypto
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/binary"
+ "io"
+ "io/ioutil"
+)
+
+type gcmEncrypter struct {
+ counter uint64
+}
+
+func (e gcmEncrypter) Signature() string {
+ return "http://www.w3.org/2009/xmlenc11#aes256-gcm"
+}
+
+func (e gcmEncrypter) GenerateKey() (ContentKey, error) {
+ slice, err := GenerateKey(aes256keyLength)
+ return ContentKey(slice), err
+}
+
+func (e *gcmEncrypter) Encrypt(key ContentKey, r io.Reader, w io.Writer) error {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return err
+ }
+
+ counter := e.counter
+ e.counter++
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return err
+ }
+
+ nonce := make([]byte, gcm.NonceSize())
+ binary.BigEndian.PutUint64(nonce, counter)
+
+ data, err := ioutil.ReadAll(r)
+ out := gcm.Seal(nonce, nonce, data, nil)
+
+ _, err = w.Write(out)
+
+ return err
+}
+
+func NewAESGCMEncrypter() Encrypter {
+ return &gcmEncrypter{}
+}
\ No newline at end of file
diff --git a/crypto/aes_gcm_test.go b/crypto/aes_gcm_test.go
new file mode 100644
index 00000000..707e1d03
--- /dev/null
+++ b/crypto/aes_gcm_test.go
@@ -0,0 +1,69 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package crypto
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/hex"
+ "testing"
+)
+
+func TestEncryptGCM(t *testing.T) {
+ key, _ := hex.DecodeString("11754cd72aec309bf52f7687212e8957")
+
+ encrypter := NewAESGCMEncrypter()
+
+ data := []byte("The quick brown fox jumps over the lazy dog")
+
+ r := bytes.NewReader(data)
+ w := new(bytes.Buffer)
+
+ if err := encrypter.Encrypt(ContentKey(key), r, w); err != nil {
+ t.Fatal("Encryption failed", err)
+ }
+
+ block, _ := aes.NewCipher(key)
+ gcm, _ := cipher.NewGCM(block)
+
+ out := w.Bytes()
+ t.Logf("nonce size: %#v", gcm.NonceSize())
+ t.Logf("nonce: %#v", out[0:gcm.NonceSize()])
+ t.Logf("ciphertext: %#v", out[gcm.NonceSize():])
+ clear := make([]byte, 0)
+ clear, err := gcm.Open(clear, out[0:gcm.NonceSize()], out[gcm.NonceSize():], nil)
+
+ if err != nil {
+ t.Fatal("Decryption failed", err)
+ }
+
+ if diff := bytes.Compare(data, clear); diff != 0 {
+ t.Logf("Original: %#v", data)
+ t.Logf("After cycle: %#v", clear)
+ t.Errorf("Expected encryption-decryption to return original")
+ }
+}
\ No newline at end of file
diff --git a/crypto/encrypt.go b/crypto/encrypt.go
index b6e668a0..6a390e1f 100644
--- a/crypto/encrypt.go
+++ b/crypto/encrypt.go
@@ -1,69 +1,72 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package crypto
import (
- "bytes"
"crypto/aes"
- "crypto/cipher"
- "crypto/rand"
"io"
)
+//"github.com/readium/readium-lcp-server/config"
+// FOR: config.Config.AES256_CBC_OR_GCM
-func Encrypt(key []byte, r io.Reader, w io.Writer) error {
- r = PaddedReader(r, aes.BlockSize)
-
- block, err := aes.NewCipher(key)
- if err != nil {
- return err
- }
-
- // generate the IV
- iv := make([]byte, aes.BlockSize)
- if _, err := io.ReadFull(rand.Reader, iv); err != nil {
- return err
- }
-
- // write the IV first
- if _, err = w.Write(iv); err != nil {
- return err
- }
-
- mode := cipher.NewCBCEncrypter(block, iv)
- buffer := make([]byte, aes.BlockSize)
- for _, err = io.ReadFull(r, buffer); err == nil; _, err = io.ReadFull(r, buffer) {
- mode.CryptBlocks(buffer, buffer)
- _, wErr := w.Write(buffer)
- if wErr != nil {
- return wErr
- }
- }
-
- if err == nil || err == io.EOF {
- return nil
- }
-
- return err
+type Encrypter interface {
+ Encrypt(key ContentKey, r io.Reader, w io.Writer) error
+ GenerateKey() (ContentKey, error)
+ Signature() string
}
-func Decrypt(key []byte, r io.Reader, w io.Writer) error {
- block, err := aes.NewCipher(key)
- if err != nil {
- return err
- }
-
- var buffer bytes.Buffer
- io.Copy(&buffer, r)
+type Decrypter interface {
+ Decrypt(key ContentKey, r io.Reader, w io.Writer) error
+}
- buf := buffer.Bytes()
- iv := buf[:aes.BlockSize]
+func NewAESEncrypter_PUBLICATION_RESOURCES() Encrypter {
+
+ return NewAESCBCEncrypter()
- mode := cipher.NewCBCDecrypter(block, iv)
- mode.CryptBlocks(buf[aes.BlockSize:], buf[aes.BlockSize:])
+ // DISABLED, see https://github.com/readium/readium-lcp-server/issues/109
+ // if config.Config.AES256_CBC_OR_GCM == "GCM" {
+ // return NewAESGCMEncrypter()
+ // } else { // default to CBC
+ // return NewAESCBCEncrypter()
+ // }
+}
- padding := buf[len(buf)-1]
- w.Write(buf[aes.BlockSize : len(buf)-int(padding)])
+func NewAESEncrypter_CONTENT_KEY() Encrypter {
+ // default to CBC
+ return NewAESCBCEncrypter()
+}
- return nil
+func NewAESEncrypter_USER_KEY_CHECK() Encrypter {
+ // default to CBC
+ return NewAESEncrypter_CONTENT_KEY()
+}
+func NewAESEncrypter_FIELDS() Encrypter {
+ // default to CBC
+ return NewAESEncrypter_CONTENT_KEY()
}
var (
diff --git a/crypto/key.go b/crypto/key.go
index 25bbf544..f0e7182a 100644
--- a/crypto/key.go
+++ b/crypto/key.go
@@ -1,15 +1,38 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package crypto
import (
"crypto/rand"
)
-const (
- keyLength = 32 // 256 bits
-)
+type ContentKey []byte
-func GenerateKey() ([]byte, error) {
- k := make([]byte, keyLength)
+func GenerateKey(size int) ([]byte, error) {
+ k := make([]byte, size)
_, err := rand.Read(k)
if err != nil {
diff --git a/crypto/key_test.go b/crypto/key_test.go
index 677d83bb..ffc3c728 100644
--- a/crypto/key_test.go
+++ b/crypto/key_test.go
@@ -1,11 +1,34 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package crypto
-import (
- "testing"
-)
+import "testing"
func TestGenerateKey(t *testing.T) {
- buf, err := GenerateKey()
+ buf, err := GenerateKey(aes256keyLength)
if err != nil {
t.Error(err)
diff --git a/crypto/pad.go b/crypto/pad.go
index a6f9f9d0..fae9b5fa 100644
--- a/crypto/pad.go
+++ b/crypto/pad.go
@@ -1,7 +1,34 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package crypto
import (
"io"
+ "math/rand"
+ "time"
)
type paddedReader struct {
@@ -10,6 +37,7 @@ type paddedReader struct {
count byte
left byte
done bool
+ insertPadLengthAll bool
}
func (r *paddedReader) Read(buf []byte) (int, error) {
@@ -48,8 +76,21 @@ func (r *paddedReader) Read(buf []byte) (int, error) {
func (r *paddedReader) pad(buf []byte) (i int, err error) {
capacity := cap(buf)
+
+ src := rand.New(rand.NewSource(time.Now().UnixNano()))
+
for i = 0; capacity > 0 && r.left > 0; i++ {
- buf[i] = r.count
+
+ if (r.insertPadLengthAll) {
+ buf[i] = r.count
+ } else {
+ if r.left == 1 { //capacity == 1 &&
+ buf[i] = r.count
+ } else {
+ buf[i] = byte(src.Intn(254) + 1)
+ }
+ }
+
capacity--
r.left--
}
@@ -61,6 +102,9 @@ func (r *paddedReader) pad(buf []byte) (i int, err error) {
return
}
-func PaddedReader(r io.Reader, blockSize byte) io.Reader {
- return &paddedReader{Reader: r, size: blockSize, count: 0, left: 0, done: false}
+
+// insertPadLengthAll = true means PKCS#7 (padding length inserted in each padding slot),
+// otherwise false means padding length inserted only in the last slot (the rest is random bytes)
+func PaddedReader(r io.Reader, blockSize byte, insertPadLengthAll bool) io.Reader {
+ return &paddedReader{Reader: r, size: blockSize, count: 0, left: 0, done: false, insertPadLengthAll: insertPadLengthAll}
}
diff --git a/crypto/pad_test.go b/crypto/pad_test.go
index 1e19a5cc..9aaeb41c 100644
--- a/crypto/pad_test.go
+++ b/crypto/pad_test.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package crypto
import (
@@ -8,7 +33,7 @@ import (
func TestOneBlock(t *testing.T) {
buf := bytes.NewBufferString("4321")
- reader := PaddedReader(buf, 6)
+ reader := PaddedReader(buf, 6, true)
var out [12]byte
n, err := reader.Read(out[:])
if err != nil && err != io.EOF {
@@ -18,6 +43,8 @@ func TestOneBlock(t *testing.T) {
t.Errorf("should have read 6 bytes, read %d", n)
}
+ // PaddedReader constructor parameter "insertPadLengthAll" is true,
+ // means all last bytes equate the padding length
if out[4] != 2 || out[5] != 2 {
t.Errorf("last values were expected to be 2, got [%x %x]", out[4], out[5])
}
@@ -25,7 +52,7 @@ func TestOneBlock(t *testing.T) {
func TestFullPadding(t *testing.T) {
buf := bytes.NewBufferString("1234")
- reader := PaddedReader(buf, 4)
+ reader := PaddedReader(buf, 4, true)
var out [8]byte
n, err := io.ReadFull(reader, out[:])
@@ -36,14 +63,16 @@ func TestFullPadding(t *testing.T) {
t.Error("should have read 8 bytes, read %d", n)
}
+ // PaddedReader constructor parameter "insertPadLengthAll" is true,
+ // means all last bytes equate the padding length
if out[4] != 4 || out[5] != 4 || out[6] != 4 || out[7] != 4 {
- t.Errorf("last values were expected to be 8, got [%x %x %x %x]", out[4], out[5], out[6], out[7])
+ t.Errorf("last values were expected to be 4, got [%x %x %x %x]", out[4], out[5], out[6], out[7])
}
}
func TestManyBlocks(t *testing.T) {
buf := bytes.NewBufferString("1234")
- reader := PaddedReader(buf, 3)
+ reader := PaddedReader(buf, 3, true)
var out [3]byte
n, err := io.ReadFull(reader, out[:])
if err != nil {
@@ -59,14 +88,80 @@ func TestManyBlocks(t *testing.T) {
t.Errorf("should have read 3 bytes, read %d", n)
}
+ // PaddedReader constructor parameter "insertPadLengthAll" is true,
+ // means all last bytes equate the padding length
if out[1] != 2 || out[2] != 2 {
t.Errorf("last values were expected to be 2, got [%x %x]", out[1], out[2])
}
}
+func TestOneBlock_Random(t *testing.T) {
+ buf := bytes.NewBufferString("4321")
+ reader := PaddedReader(buf, 6, false)
+ var out [12]byte
+ n, err := reader.Read(out[:])
+ if err != nil && err != io.EOF {
+ t.Error(err)
+ }
+ if n != 6 {
+ t.Errorf("should have read 6 bytes, read %d", n)
+ }
+
+ // the PaddedReader constructor parameter "insertPadLengthAll" is false,
+ // so only the last byte out[2] equates the padding length (the others are random)
+ if out[4] == 2 || out[5] != 2 {
+ t.Errorf("last values were expected to be [random, 2], got [%x %x]", out[4], out[5])
+ }
+}
+
+func TestFullPadding_Random(t *testing.T) {
+ buf := bytes.NewBufferString("1234")
+ reader := PaddedReader(buf, 4, false)
+
+ var out [8]byte
+ n, err := io.ReadFull(reader, out[:])
+ if err != nil {
+ t.Error(err)
+ }
+ if n != 8 {
+ t.Error("should have read 8 bytes, read %d", n)
+ }
+
+ // the PaddedReader constructor parameter "insertPadLengthAll" is false,
+ // so only the last byte out[7] equates the padding length (the others are random)
+ if out[4] == 4 || out[5] == 4 || out[6] == 4 || out[7] != 4 {
+ t.Errorf("last values were expected to be [random, random, random, 4], got [%x %x %x %x]", out[4], out[5], out[6], out[7])
+ }
+}
+
+func TestManyBlocks_Random(t *testing.T) {
+ buf := bytes.NewBufferString("1234")
+ reader := PaddedReader(buf, 3, false)
+ var out [3]byte
+ n, err := io.ReadFull(reader, out[:])
+ if err != nil {
+ t.Error(err)
+ }
+
+ n, err = io.ReadFull(reader, out[:])
+ if err != nil {
+ t.Error(err)
+ }
+
+ if n != 3 {
+ t.Errorf("should have read 3 bytes, read %d", n)
+ }
+
+ // the PaddedReader constructor parameter "insertPadLengthAll" is false,
+ // so only the last byte out[2] equates the padding length (the others are random)
+ if out[1] == 2 || out[2] != 2 {
+ t.Errorf("last values were expected to be [random 2], got [%x %x]", out[1], out[2])
+ }
+}
+
func TestPaddingInMultipleCalls(t *testing.T) {
buf := bytes.NewBufferString("1")
- reader := PaddedReader(buf, 6)
+ reader := PaddedReader(buf, 6, false)
var out [3]byte
n, err := io.ReadFull(reader, out[:])
@@ -97,7 +192,7 @@ func (r failingReader) Read(buf []byte) (int, error) {
}
func TestFailingReader(t *testing.T) {
- reader := PaddedReader(failingReader{}, 8)
+ reader := PaddedReader(failingReader{}, 8, false)
var out [8]byte
_, err := io.ReadFull(reader, out[:])
diff --git a/dbmodel/Database Model Diagram.png b/dbmodel/Database Model Diagram.png
new file mode 100644
index 00000000..b7121d03
Binary files /dev/null and b/dbmodel/Database Model Diagram.png differ
diff --git a/dbmodel/database script.sql b/dbmodel/database script.sql
new file mode 100644
index 00000000..49807736
--- /dev/null
+++ b/dbmodel/database script.sql
@@ -0,0 +1,47 @@
+
+CREATE TABLE IF NOT EXISTS content (
+ id varchar(255) PRIMARY KEY NOT NULL,
+ encryption_key varchar(64) NOT NULL,
+ location text NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS license (
+ id varchar(255) PRIMARY KEY NOT NULL,
+ user_id varchar(255) NOT NULL,
+ provider varchar(255) NOT NULL,
+ issued datetime NOT NULL,
+ updated datetime DEFAULT NULL,
+ rights_print int(11) DEFAULT NULL,
+ rights_copy int(11) DEFAULT NULL,
+ rights_start datetime DEFAULT NULL,
+ rights_end datetime DEFAULT NULL,
+ user_key_hint text NOT NULL,
+ user_key_hash varchar(64) NOT NULL,
+ user_key_algorithm varchar(255) NOT NULL,
+ content_fk varchar(255) NOT NULL,
+ FOREIGN KEY(content_fk) REFERENCES content(id)
+);
+
+CREATE TABLE IF NOT EXISTS license_status (
+ id INTEGER PRIMARY KEY,
+ status int(11) NOT NULL,
+ license_updated datetime NOT NULL,
+ status_updated datetime NOT NULL,
+ device_count int(11) DEFAULT NULL,
+ potential_rights_end datetime DEFAULT NULL,
+ license_ref varchar(255) NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS license_ref_index on license_status (license_ref);
+
+CREATE TABLE IF NOT EXISTS event (
+ id INTEGER PRIMARY KEY,
+ device_name varchar(255) DEFAULT NULL,
+ timestamp datetime NOT NULL,
+ type int NOT NULL,
+ device_id varchar(255) DEFAULT NULL,
+ license_status_fk int NOT NULL,
+ FOREIGN KEY(license_status_fk) REFERENCES license_status(id)
+);
+
+CREATE INDEX IF NOT EXISTS license_status_fk_index on event (license_status_fk);
diff --git a/epub/epub.go b/epub/epub.go
index 44dc5b76..37a85ab1 100644
--- a/epub/epub.go
+++ b/epub/epub.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package epub
import (
@@ -5,11 +30,23 @@ import (
"path/filepath"
"sort"
"strings"
+ "io"
"github.com/readium/readium-lcp-server/epub/opf"
"github.com/readium/readium-lcp-server/xmlenc"
+)
- "io"
+const (
+ ContainerFile = "META-INF/container.xml"
+ EncryptionFile = "META-INF/encryption.xml"
+ LicenseFile = "META-INF/license.lcpl"
+
+ ContentType_XHTML = "application/xhtml+xml"
+ ContentType_HTML = "text/html"
+
+ ContentType_NCX = "application/x-dtbncx+xml"
+
+ ContentType_EPUB = "application/epub+zip"
)
type Epub struct {
diff --git a/epub/opf/opf.go b/epub/opf/opf.go
index 3195d580..21b79bd6 100644
--- a/epub/opf/opf.go
+++ b/epub/opf/opf.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package opf
import (
diff --git a/epub/reader.go b/epub/reader.go
index 6cccbc9d..1fac8428 100644
--- a/epub/reader.go
+++ b/epub/reader.go
@@ -1,21 +1,43 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package epub
import (
"archive/zip"
"encoding/xml"
"path/filepath"
-
- "github.com/readium/readium-lcp-server/epub/opf"
- "github.com/readium/readium-lcp-server/xmlenc"
-
"io"
"sort"
"strings"
+
+ "github.com/readium/readium-lcp-server/epub/opf"
+ "github.com/readium/readium-lcp-server/xmlenc"
)
const (
- ContainerFile = "META-INF/container.xml"
- EncryptionFile = "META-INF/encryption.xml"
RootFileElement = "rootfile"
)
@@ -164,7 +186,7 @@ func addCleartextResources(ep *Epub, p opf.Package) {
for _, item := range p.Manifest.Items {
if strings.Contains(item.Properties, "cover-image") ||
strings.Contains(item.Properties, "nav") ||
- item.MediaType == "application/x-dtbncx+xml" {
+ item.MediaType == ContentType_NCX {
ep.addCleartextResource(filepath.Join(p.BasePath, item.Href))
}
}
diff --git a/epub/reader_test.go b/epub/reader_test.go
index 959adad3..15786002 100644
--- a/epub/reader_test.go
+++ b/epub/reader_test.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package epub
import (
@@ -28,7 +53,7 @@ func TestEpubLoading(t *testing.T) {
t.Error("Expected 1 opf, got %d", len(ep.Package))
}
- expectedCleartext := []string{"META-INF/container.xml", "OPS/package.opf", "OPS/images/9780316000000.jpg", "OPS/toc.xhtml"}
+ expectedCleartext := []string{ContainerFile, "OPS/package.opf", "OPS/images/9780316000000.jpg", "OPS/toc.xhtml"}
sort.Strings(expectedCleartext)
if fmt.Sprintf("%v", ep.cleartextResources) != fmt.Sprintf("%v", expectedCleartext) {
t.Errorf("Cleartext resources, expected %v, got %v", expectedCleartext, ep.cleartextResources)
@@ -38,7 +63,7 @@ func TestEpubLoading(t *testing.T) {
t.Error("Expected a cover to be found")
}
- if expected := "application/xhtml+xml"; ep.Resource[2].ContentType != expected {
+ if expected := ContentType_XHTML; ep.Resource[2].ContentType != expected {
t.Errorf("Content Type matching, expected %v, got %v", expected, ep.Resource[2].ContentType)
}
}
diff --git a/epub/utils.go b/epub/utils.go
index c40902ea..704d2377 100644
--- a/epub/utils.go
+++ b/epub/utils.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package epub
import (
diff --git a/epub/writer.go b/epub/writer.go
index b8a027f9..38186da2 100644
--- a/epub/writer.go
+++ b/epub/writer.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package epub
import (
@@ -7,10 +32,6 @@ import (
"github.com/readium/readium-lcp-server/xmlenc"
)
-const (
- mimetype = "application/epub+zip"
-)
-
type Writer struct {
w *zip.Writer
}
@@ -36,7 +57,7 @@ func (w *Writer) Copy(r *Resource) error {
}
func (w *Writer) WriteEncryption(enc *xmlenc.Manifest) error {
- fw, err := w.AddResource("META-INF/encryption.xml", zip.Deflate)
+ fw, err := w.AddResource(EncryptionFile, zip.Deflate)
if err != nil {
return err
}
@@ -97,7 +118,7 @@ func writeMimetype(w *zip.Writer) error {
return err
}
- wf.Write([]byte(mimetype))
+ wf.Write([]byte(ContentType_EPUB))
return nil
}
diff --git a/epub/writer_test.go b/epub/writer_test.go
index afc0d78c..2634cc95 100644
--- a/epub/writer_test.go
+++ b/epub/writer_test.go
@@ -1,3 +1,28 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
package epub
import (
@@ -39,7 +64,7 @@ const basicPage = `
func createBasicEpub() Epub {
var ep Epub
- ep.Add("META-INF/container.xml", strings.NewReader(containerSpec), uint64(len(containerSpec)))
+ ep.Add(ContainerFile, strings.NewReader(containerSpec), uint64(len(containerSpec)))
ep.Add("EPUB/package.opf", strings.NewReader(basicOpf), uint64(len(basicOpf)))
@@ -79,8 +104,8 @@ func TestWriteBasicEpub(t *testing.T) {
}
}
- testContentsOfFileInZip(t, zr, zip.Store, "mimetype", "application/epub+zip")
- testContentsOfFileInZip(t, zr, zip.Deflate, "META-INF/container.xml", containerSpec)
+ testContentsOfFileInZip(t, zr, zip.Store, "mimetype", ContentType_EPUB)
+ testContentsOfFileInZip(t, zr, zip.Deflate, ContainerFile, containerSpec)
testContentsOfFileInZip(t, zr, zip.Deflate, "EPUB/package.opf", basicOpf)
testContentsOfFileInZip(t, zr, zip.Deflate, "EPUB/page.xhtml", basicPage)
}
diff --git a/frontend/api/common.go b/frontend/api/common.go
new file mode 100644
index 00000000..c18e108d
--- /dev/null
+++ b/frontend/api/common.go
@@ -0,0 +1,106 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package staticapi
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/readium/readium-lcp-server/api"
+ "github.com/readium/readium-lcp-server/frontend/webpublication"
+ "github.com/readium/readium-lcp-server/frontend/webpurchase"
+ "github.com/readium/readium-lcp-server/frontend/webrepository"
+ "github.com/readium/readium-lcp-server/frontend/webuser"
+)
+
+//IServer defines methods for db interaction
+type IServer interface {
+ RepositoryAPI() webrepository.WebRepository
+ PublicationAPI() webpublication.WebPublication
+ UserAPI() webuser.WebUser
+ PurchaseAPI() webpurchase.WebPurchase
+}
+
+// Pagination used to paginate listing
+type Pagination struct {
+ Page int
+ PerPage int
+}
+
+// ExtractPaginationFromRequest extract from http.Request pagination information
+func ExtractPaginationFromRequest(r *http.Request) (Pagination, error) {
+ var err error
+ var page int64 // default: page 1
+ var perPage int64 // default: 30 items per page
+ pagination := Pagination{}
+
+ if r.FormValue("page") != "" {
+ page, err = strconv.ParseInt((r).FormValue("page"), 10, 32)
+ if err != nil {
+ return pagination, err
+ }
+ } else {
+ page = 1
+ }
+
+ if r.FormValue("per_page") != "" {
+ perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32)
+ if err != nil {
+ return pagination, err
+ }
+ } else {
+ perPage = 30
+ }
+
+ if page > 0 {
+ page-- //pagenum starting at 0 in code, but user interface starting at 1
+ }
+
+ if page < 0 {
+ return pagination, err
+ }
+
+ pagination.Page = int(page)
+ pagination.PerPage = int(perPage)
+ return pagination, err
+}
+
+// PrepareListHeaderResponse Set http headers
+func PrepareListHeaderResponse(
+ resourceCount int,
+ resourceLink string,
+ pagination Pagination,
+ w http.ResponseWriter) {
+ if resourceCount > 0 {
+ nextPage := strconv.Itoa(int(pagination.Page) + 1)
+ w.Header().Set("Link", "<"+resourceLink+"?page="+nextPage+">; rel=\"next\"; title=\"next\"")
+ }
+ if pagination.Page > 1 {
+ previousPage := strconv.Itoa(int(pagination.Page) - 1)
+ w.Header().Set("Link", "<"+resourceLink+"/?page="+previousPage+">; rel=\"previous\"; title=\"previous\"")
+ }
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+}
diff --git a/frontend/api/publication.go b/frontend/api/publication.go
new file mode 100644
index 00000000..b774e972
--- /dev/null
+++ b/frontend/api/publication.go
@@ -0,0 +1,213 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package staticapi
+
+import (
+ "encoding/json"
+ "net/http"
+ "strconv"
+
+ "github.com/gorilla/mux"
+ "github.com/readium/readium-lcp-server/api"
+ "github.com/readium/readium-lcp-server/frontend/webpublication"
+ "github.com/readium/readium-lcp-server/problem"
+)
+
+//GetPublications returns a list of publications
+func GetPublications(w http.ResponseWriter, r *http.Request, s IServer) {
+ var page int64
+ var perPage int64
+ var err error
+
+ if r.FormValue("page") != "" {
+ page, err = strconv.ParseInt((r).FormValue("page"), 10, 32)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ } else {
+ page = 1
+ }
+
+ if r.FormValue("per_page") != "" {
+ perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ } else {
+ perPage = 30
+ }
+
+ if page > 0 {
+ page-- //pagenum starting at 0 in code, but user interface starting at 1
+ }
+
+ if page < 0 {
+ problem.Error(w, r, problem.Problem{Detail: "page must be positive integer"}, http.StatusBadRequest)
+ return
+ }
+
+ pubs := make([]webpublication.Publication, 0)
+ //log.Println("ListAll(" + strconv.Itoa(int(per_page)) + "," + strconv.Itoa(int(page)) + ")")
+ fn := s.PublicationAPI().List(int(perPage), int(page))
+ for it, err := fn(); err == nil; it, err = fn() {
+ pubs = append(pubs, it)
+ }
+ if len(pubs) > 0 {
+ nextPage := strconv.Itoa(int(page) + 1)
+ w.Header().Set("Link", "; rel=\"next\"; title=\"next\"")
+ }
+ if page > 1 {
+ previousPage := strconv.Itoa(int(page) - 1)
+ w.Header().Set("Link", "; rel=\"previous\"; title=\"previous\"")
+ }
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+
+ enc := json.NewEncoder(w)
+ err = enc.Encode(pubs)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+}
+
+// GetPublicationByUUID searches a publication by its uuid
+func GetPublication(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ var id int
+ var err error
+ if id, err = strconv.Atoi(vars["id"]); err != nil {
+ // id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "Plublication ID must be an integer"}, http.StatusBadRequest)
+ }
+
+ if pub, err := s.PublicationAPI().Get(int64(id)); err == nil {
+ enc := json.NewEncoder(w)
+ if err = enc.Encode(pub); err == nil {
+ // send json of correctly encoded user info
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ } else {
+ switch err {
+ case webpublication.ErrNotFound:
+ {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ }
+ default:
+ {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ }
+ }
+}
+
+//DecodeJSONUser transforms a json string to a User struct
+func DecodeJSONPublication(r *http.Request) (webpublication.Publication, error) {
+ var dec *json.Decoder
+ if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_JSON {
+ dec = json.NewDecoder(r.Body)
+ }
+ pub := webpublication.Publication{}
+ err := dec.Decode(&pub)
+ return pub, err
+}
+
+// CreatePublication creates a publication in the database
+func CreatePublication(w http.ResponseWriter, r *http.Request, s IServer) {
+ var pub webpublication.Publication
+ var err error
+ if pub, err = DecodeJSONPublication(r); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: "incorrect JSON Publication " + err.Error()}, http.StatusBadRequest)
+ return
+ }
+ // publication ok
+ if err := s.PublicationAPI().Add(pub); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+
+ // publication added to db
+ w.WriteHeader(http.StatusCreated)
+}
+
+// UpdatePublication updates an identified publication (id) in the database
+func UpdatePublication(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ var id int
+ var err error
+ var pub webpublication.Publication
+ if id, err = strconv.Atoi(vars["id"]); err != nil {
+ // id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "Plublication ID must be an integer"}, http.StatusBadRequest)
+ return
+ }
+ // ID is a number, check publication (json)
+ if pub, err = DecodeJSONPublication(r); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ // publication ok, id is a number, search publication to update
+ if foundPub, err := s.PublicationAPI().Get(int64(id)); err != nil {
+ switch err {
+ case webpublication.ErrNotFound:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ default:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ } else {
+ // publication is found!
+ if err := s.PublicationAPI().Update(webpublication.Publication{
+ ID: foundPub.ID,
+ Title: pub.Title,
+ Status: foundPub.Status}); err != nil {
+ //update failed!
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ //database update ok
+ w.WriteHeader(http.StatusOK)
+ //return
+ }
+}
+
+// DeletePublication removes a publication in the database
+func DeletePublication(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ id, err := strconv.ParseInt(vars["id"], 10, 64)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ if err := s.PublicationAPI().Delete(id); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ // publication deleted from db
+ w.WriteHeader(http.StatusOK)
+}
diff --git a/frontend/api/purchase.go b/frontend/api/purchase.go
new file mode 100644
index 00000000..851f7118
--- /dev/null
+++ b/frontend/api/purchase.go
@@ -0,0 +1,314 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package staticapi
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "strconv"
+
+ "github.com/gorilla/mux"
+ "github.com/readium/readium-lcp-server/api"
+ "github.com/readium/readium-lcp-server/frontend/webpurchase"
+ "github.com/readium/readium-lcp-server/license"
+ "github.com/readium/readium-lcp-server/problem"
+
+ "github.com/Machiel/slugify"
+)
+
+//DecodeJSONPurchase transforms a json string to a User struct
+func DecodeJSONPurchase(r *http.Request) (webpurchase.Purchase, error) {
+ var dec *json.Decoder
+ if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_JSON {
+ dec = json.NewDecoder(r.Body)
+ }
+ purchase := webpurchase.Purchase{}
+ err := dec.Decode(&purchase)
+ return purchase, err
+}
+
+// GetPurchases searches all purchases for a client
+func GetPurchases(w http.ResponseWriter, r *http.Request, s IServer) {
+ var err error
+
+ pagination, err := ExtractPaginationFromRequest(r)
+ if err != nil {
+ // user id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "Pagination error"}, http.StatusBadRequest)
+ return
+ }
+
+ purchases := make([]webpurchase.Purchase, 0)
+ fn := s.PurchaseAPI().List(pagination.PerPage, pagination.Page)
+
+ for it, err := fn(); err == nil; it, err = fn() {
+ purchases = append(purchases, it)
+ }
+
+ enc := json.NewEncoder(w)
+ err = enc.Encode(purchases)
+ PrepareListHeaderResponse(len(purchases), "/api/v1/purchases", pagination, w)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+}
+
+//GetUserPurchases searches all purchases for a client
+func GetUserPurchases(w http.ResponseWriter, r *http.Request, s IServer) {
+ var err error
+ var userId int64
+ vars := mux.Vars(r)
+
+ if userId, err = strconv.ParseInt(vars["user_id"], 10, 64); err != nil {
+ // user id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "User ID must be an integer"}, http.StatusBadRequest)
+ return
+ }
+
+ pagination, err := ExtractPaginationFromRequest(r)
+ if err != nil {
+ // user id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "Pagination error"}, http.StatusBadRequest)
+ return
+ }
+
+ purchases := make([]webpurchase.Purchase, 0)
+ fn := s.PurchaseAPI().ListByUser(userId, pagination.PerPage, pagination.Page)
+ for it, err := fn(); err == nil; it, err = fn() {
+ purchases = append(purchases, it)
+ }
+
+ enc := json.NewEncoder(w)
+ err = enc.Encode(purchases)
+ PrepareListHeaderResponse(len(purchases), "/api/v1/users/"+vars["user_id"]+"/purchases", pagination, w)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+}
+
+//CreatePurchase creates a purchase in the database
+func CreatePurchase(w http.ResponseWriter, r *http.Request, s IServer) {
+ var purchase webpurchase.Purchase
+ var err error
+ if purchase, err = DecodeJSONPurchase(r); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: "incorrect JSON Purchase " + err.Error()}, http.StatusBadRequest)
+ return
+ }
+
+ // purchase ok
+ if err = s.PurchaseAPI().Add(purchase); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ return
+ }
+
+ // publication added to db
+ w.WriteHeader(http.StatusCreated)
+}
+
+//GetPurchaseLicenseFromLicenseUUID() finds the purchase ID from a given license UUID (passed in URL),
+//and performs the same as GetPurchaseLicense(), returning "license.lcpl" filename
+//(as this API is meant to be accessed from the LSD JSON license link)
+func GetPurchaseLicenseFromLicenseUUID(w http.ResponseWriter, r *http.Request, s IServer) {
+
+ vars := mux.Vars(r)
+ var purchase webpurchase.Purchase
+ var err error
+
+ if purchase, err = s.PurchaseAPI().GetByLicenseID(vars["licenseID"]); err != nil {
+ switch err {
+ case webpurchase.ErrNotFound:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ default:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ return
+ }
+
+ fullLicense, err := s.PurchaseAPI().GenerateLicense(purchase)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ return
+ }
+
+ //attachmentName := slugify.Slugify(purchase.Publication.Title)
+ w.Header().Set("Content-Type", api.ContentType_LCP_JSON)
+ w.Header().Set("Content-Disposition", "attachment; filename=\"license.lcpl\"")
+
+ enc := json.NewEncoder(w)
+ err = enc.Encode(fullLicense)
+
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ return
+ }
+}
+
+//GetPurchaseLicense contacts LCP server and asks a license for the purchase using the partial license and resourceID
+func GetPurchaseLicense(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ var id int
+ var err error
+
+ if id, err = strconv.Atoi(vars["id"]); err != nil {
+ // id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "Purchase ID must be an integer"}, http.StatusBadRequest)
+ return
+ }
+
+ purchase, err := s.PurchaseAPI().Get(int64(id))
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ return
+ }
+
+ fullLicense, err := s.PurchaseAPI().GenerateLicense(purchase)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ return
+ }
+
+ attachmentName := slugify.Slugify(purchase.Publication.Title)
+ w.Header().Set("Content-Type", api.ContentType_LCP_JSON)
+ w.Header().Set("Content-Disposition", "attachment; filename=\""+attachmentName+".lcpl\"")
+
+ enc := json.NewEncoder(w)
+ err = enc.Encode(fullLicense)
+
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ return
+ }
+}
+
+//GetPurchase gets a purchase by its ID in the database
+func GetPurchase(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ var id int
+ var err error
+ if id, err = strconv.Atoi(vars["id"]); err != nil {
+ // id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "Purchase ID must be an integer"}, http.StatusBadRequest)
+ return
+ }
+
+ purchase, err := s.PurchaseAPI().Get(int64(id))
+ if err != nil {
+ switch err {
+ case webpurchase.ErrNotFound:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ default:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ return
+ }
+
+ // purchase found
+ // purchase.PartialLicense = "*" //hide partialLicense?
+ enc := json.NewEncoder(w)
+ if err = enc.Encode(purchase); err == nil {
+ // send json of correctly encoded user info
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+}
+
+//GetPurchaseByLicenseID gets a purchase by a LicenseID in the database
+func GetPurchaseByLicenseID(w http.ResponseWriter, r *http.Request, s IServer) {
+ var purchase webpurchase.Purchase
+ vars := mux.Vars(r)
+ var err error
+
+ if purchase, err = s.PurchaseAPI().GetByLicenseID(vars["licenseID"]); err != nil {
+ switch err {
+ case webpurchase.ErrNotFound:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ default:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ return
+ }
+ // purchase found
+ enc := json.NewEncoder(w)
+ if err = enc.Encode(purchase); err == nil {
+ // send json of correctly encoded user info
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+}
+
+// getLicenseInfo decoldes a license in data (bytes, response.body)
+func getLicenseInfo(data []byte, lic *license.License) error {
+ var dec *json.Decoder
+ dec = json.NewDecoder(bytes.NewReader(data))
+ if err := dec.Decode(&lic); err != nil {
+ return err
+ }
+ return nil
+}
+
+//UpdatePurchase updates a purchase in the database
+func UpdatePurchase(w http.ResponseWriter, r *http.Request, s IServer) {
+ var newPurchase webpurchase.Purchase
+ vars := mux.Vars(r)
+ var id int
+ var err error
+ if id, err = strconv.Atoi(vars["id"]); err != nil {
+ // id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "Purchase ID must be an integer"}, http.StatusBadRequest)
+ return
+ }
+ //ID is a number, check user (json)
+ if newPurchase, err = DecodeJSONPurchase(r); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+
+ // purchase found
+ if err := s.PurchaseAPI().Update(webpurchase.Purchase{
+ ID: int64(id),
+ LicenseUUID: newPurchase.LicenseUUID,
+ StartDate: newPurchase.StartDate,
+ EndDate: newPurchase.EndDate,
+ Status: newPurchase.Status}); err != nil {
+
+ switch err {
+ case webpurchase.ErrNotFound:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ default:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ return
+ }
+
+ w.WriteHeader(http.StatusOK)
+}
diff --git a/frontend/api/repository.go b/frontend/api/repository.go
new file mode 100644
index 00000000..2eaa2dc5
--- /dev/null
+++ b/frontend/api/repository.go
@@ -0,0 +1,57 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package staticapi
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/readium/readium-lcp-server/api"
+ "github.com/readium/readium-lcp-server/frontend/webrepository"
+ "github.com/readium/readium-lcp-server/problem"
+)
+
+// GetRepositoryMasterFiles returns a list of repository masterfiles
+func GetRepositoryMasterFiles(w http.ResponseWriter, r *http.Request, s IServer) {
+ var err error
+
+ files := make([]webrepository.RepositoryFile, 0)
+
+ fn := s.RepositoryAPI().GetMasterFiles()
+
+ for it, err := fn(); err == nil; it, err = fn() {
+ files = append(files, it)
+ }
+
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+
+ enc := json.NewEncoder(w)
+ err = enc.Encode(files)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+}
diff --git a/frontend/api/user.go b/frontend/api/user.go
new file mode 100644
index 00000000..15c0edee
--- /dev/null
+++ b/frontend/api/user.go
@@ -0,0 +1,205 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package staticapi
+
+import (
+ "encoding/json"
+ "net/http"
+ "strconv"
+
+ "github.com/gorilla/mux"
+ "github.com/readium/readium-lcp-server/api"
+ "github.com/readium/readium-lcp-server/frontend/webuser"
+ "github.com/readium/readium-lcp-server/problem"
+)
+
+//GetUsers returns a list of users
+func GetUsers(w http.ResponseWriter, r *http.Request, s IServer) {
+ var page int64
+ var perPage int64
+ var err error
+ if r.FormValue("page") != "" {
+ page, err = strconv.ParseInt((r).FormValue("page"), 10, 32)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ } else {
+ page = 1
+ }
+ if r.FormValue("per_page") != "" {
+ perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ } else {
+ perPage = 30
+ }
+ if page > 0 {
+ page-- //pagenum starting at 0 in code, but user interface starting at 1
+ }
+ if page < 0 {
+ problem.Error(w, r, problem.Problem{Detail: "page must be positive integer"}, http.StatusBadRequest)
+ return
+ }
+ users := make([]webuser.User, 0)
+ //log.Println("ListAll(" + strconv.Itoa(int(per_page)) + "," + strconv.Itoa(int(page)) + ")")
+ fn := s.UserAPI().ListUsers(int(perPage), int(page))
+ for it, err := fn(); err == nil; it, err = fn() {
+ users = append(users, it)
+ }
+ if len(users) > 0 {
+ nextPage := strconv.Itoa(int(page) + 1)
+ w.Header().Set("Link", "; rel=\"next\"; title=\"next\"")
+ }
+ if page > 1 {
+ previousPage := strconv.Itoa(int(page) - 1)
+ w.Header().Set("Link", "; rel=\"previous\"; title=\"previous\"")
+ }
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+
+ enc := json.NewEncoder(w)
+ err = enc.Encode(users)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+}
+
+//GetUserByEmail searches a client by his email
+func GetUser(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ id, err := strconv.Atoi(vars["id"])
+ if err != nil {
+ // id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "User ID must be an integer"}, http.StatusBadRequest)
+ }
+ if user, err := s.UserAPI().Get(int64(id)); err == nil {
+ enc := json.NewEncoder(w)
+ if err = enc.Encode(user); err == nil {
+ // send json of correctly encoded user info
+ w.Header().Set("Content-Type", api.ContentType_JSON)
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ } else {
+ switch err {
+ case webuser.ErrNotFound:
+ {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ }
+ default:
+ {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ }
+ }
+ return
+}
+
+//DecodeJSONUser transforms a json string to a User struct
+func DecodeJSONUser(r *http.Request) (webuser.User, error) {
+ var dec *json.Decoder
+ if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_JSON {
+ dec = json.NewDecoder(r.Body)
+ }
+ user := webuser.User{}
+ err := dec.Decode(&user)
+ return user, err
+}
+
+//CreateUser creates a user in the database
+func CreateUser(w http.ResponseWriter, r *http.Request, s IServer) {
+ var user webuser.User
+ var err error
+ if user, err = DecodeJSONUser(r); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: "incorrect JSON User " + err.Error()}, http.StatusBadRequest)
+ return
+ }
+ //user ok
+ if err := s.UserAPI().Add(user); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ // user added to db
+ w.WriteHeader(http.StatusCreated)
+}
+
+//UpdateUser updates an identified user (id) in the database
+func UpdateUser(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ var id int
+ var err error
+ var user webuser.User
+ if id, err = strconv.Atoi(vars["id"]); err != nil {
+ // id is not a number
+ problem.Error(w, r, problem.Problem{Detail: "User ID must be an integer"}, http.StatusBadRequest)
+ return
+ }
+ //ID is a number, check user (json)
+ if user, err = DecodeJSONUser(r); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ // user ok, id is a number, search user to update
+ if _, err := s.UserAPI().Get(int64(id)); err != nil {
+ switch err {
+ case webuser.ErrNotFound:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound)
+ default:
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ }
+ } else {
+ // client is found!
+ if err := s.UserAPI().Update(webuser.User{ID: int64(id), Name: user.Name, Email: user.Email, Password: user.Password}); err != nil {
+ //update failed!
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError)
+ return
+ }
+ //database update ok
+ w.WriteHeader(http.StatusOK)
+ //return
+ }
+
+}
+
+//DeleteUser creates a user in the database
+func DeleteUser(w http.ResponseWriter, r *http.Request, s IServer) {
+ vars := mux.Vars(r)
+ uid, err := strconv.ParseInt(vars["id"], 10, 64)
+ if err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ if err := s.UserAPI().DeleteUser(uid); err != nil {
+ problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest)
+ return
+ }
+ // user added to db
+ w.WriteHeader(http.StatusOK)
+}
diff --git a/frontend/frontend.go b/frontend/frontend.go
new file mode 100644
index 00000000..d44869b1
--- /dev/null
+++ b/frontend/frontend.go
@@ -0,0 +1,169 @@
+// Copyright (c) 2016 Readium Foundation
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation and/or
+// other materials provided with the distribution.
+// 3. Neither the name of the organization nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package main
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+
+ _ "github.com/go-sql-driver/mysql"
+ _ "github.com/lib/pq"
+ _ "github.com/mattn/go-sqlite3"
+
+ "github.com/readium/readium-lcp-server/config"
+ "github.com/readium/readium-lcp-server/frontend/server"
+ "github.com/readium/readium-lcp-server/frontend/webpublication"
+ "github.com/readium/readium-lcp-server/frontend/webpurchase"
+ "github.com/readium/readium-lcp-server/frontend/webrepository"
+ "github.com/readium/readium-lcp-server/frontend/webuser"
+)
+
+func dbFromURI(uri string) (string, string) {
+ parts := strings.Split(uri, "://")
+ return parts[0], parts[1]
+}
+
+func main() {
+ var dbURI, static, configFile string
+ var err error
+
+ if configFile = os.Getenv("READIUM_WEBTEST_CONFIG"); configFile == "" {
+ configFile = "config.yaml"
+ }
+ config.ReadConfig(configFile)
+ log.Println("Read config from " + configFile)
+
+ err = config.SetPublicUrls()
+ if err != nil {
+ panic(err)
+ }
+
+ log.Println("LCP server = " + config.Config.LcpServer.PublicBaseUrl)
+ log.Println("using login " + config.Config.LcpUpdateAuth.Username)
+
+ if dbURI = config.Config.FrontendServer.Database; dbURI == "" {
+ dbURI = "sqlite3://file:frontend.sqlite?cache=shared&mode=rwc"
+ }
+ driver, cnxn := dbFromURI(dbURI)
+ db, err := sql.Open(driver, cnxn)
+ if err != nil {
+ panic(err)
+ }
+ _, err = db.Exec("PRAGMA journal_mode = WAL")
+ if err != nil {
+ panic(err)
+ }
+
+ repoManager, err := webrepository.Init(config.Config.FrontendServer)
+ if err != nil {
+ panic(err)
+ }
+
+ publicationDB, err := webpublication.Init(config.Config, db)
+ if err != nil {
+ panic(err)
+ }
+
+ userDB, err := webuser.Open(db)
+ if err != nil {
+ panic(err)
+ }
+
+ purchaseDB, err := webpurchase.Init(config.Config, db)
+ if err != nil {
+ panic(err)
+ }
+
+ static = config.Config.FrontendServer.Directory
+ if static == "" {
+ _, file, _, _ := runtime.Caller(0)
+ here := filepath.Dir(file)
+ static = filepath.Join(here, "../frontend/manage")
+ }
+
+ filepathConfigJs := filepath.Join(static, "config.js")
+ fileConfigJs, err := os.Create(filepathConfigJs)
+ if err != nil {
+ panic(err)
+ }
+
+ defer func() {
+ if err := fileConfigJs.Close(); err != nil {
+ panic(err)
+ }
+ }()
+
+ configJs := `
+ // This file is automatically generated, and git-ignored.
+ // To ignore your local changes, use:
+ // git update-index --assume-unchanged frontend/manage/config.js
+ window.Config = {`
+ configJs += "\n\tfrontend: {url: '" + config.Config.FrontendServer.PublicBaseUrl + "' },\n"
+ configJs += "\tlcp: {url: '" + config.Config.LcpServer.PublicBaseUrl + "'},\n"
+ configJs += "\tlsd: {url: '" + config.Config.LsdServer.PublicBaseUrl + "'}\n}"
+
+ log.Println("manage/index.html config.js:")
+ log.Println(configJs)
+
+ fileConfigJs.WriteString(configJs)
+ HandleSignals()
+ s := frontend.New(config.Config.FrontendServer.Host+":"+strconv.Itoa(config.Config.FrontendServer.Port), static, repoManager, publicationDB, userDB, purchaseDB)
+ log.Println("Frontend webserver for LCP running on " + config.Config.FrontendServer.Host + ":" + strconv.Itoa(config.Config.FrontendServer.Port))
+ log.Println("using database " + dbURI)
+
+ if err := s.ListenAndServe(); err != nil {
+ log.Println("Error " + err.Error())
+ }
+}
+
+// HandleSignals handles system signals and adds a log before quitting
+func HandleSignals() {
+ sigChan := make(chan os.Signal)
+ go func() {
+ stacktrace := make([]byte, 1<<20)
+ for sig := range sigChan {
+ switch sig {
+ case syscall.SIGQUIT:
+ length := runtime.Stack(stacktrace, true)
+ fmt.Println(string(stacktrace[:length]))
+ case syscall.SIGINT:
+ fallthrough
+ case syscall.SIGTERM:
+ fmt.Println("Shutting down...")
+ os.Exit(0)
+ }
+ }
+ }()
+ signal.Notify(sigChan, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM)
+}
diff --git a/frontend/manage/.editorconfig b/frontend/manage/.editorconfig
new file mode 100644
index 00000000..928602c5
--- /dev/null
+++ b/frontend/manage/.editorconfig
@@ -0,0 +1,28 @@
+# http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+# 4 spaces indentation
+[*.{ts,scss,css}]
+indent_style = space
+indent_size = 4
+
+# 2 spaces indentation
+[*.{html}]
+indent_style = space
+indent_size = 2
+
+[*.md]
+max_line_length = 0
+trim_trailing_whitespace = false
+
+# Indentation override
+#[lib/**.js]
+#[{package.json,.travis.yml}]
+#[**/**.js]
diff --git a/frontend/manage/.travis.yml b/frontend/manage/.travis.yml
new file mode 100644
index 00000000..20ff41e7
--- /dev/null
+++ b/frontend/manage/.travis.yml
@@ -0,0 +1,20 @@
+dist: trusty
+sudo: required
+language: node_js
+node_js:
+ - "5"
+os:
+ - linux
+env:
+ global:
+ - DBUS_SESSION_BUS_ADDRESS=/dev/null
+ - DISPLAY=:99.0
+ - CHROME_BIN=chromium-browser
+before_script:
+ - sh -e /etc/init.d/xvfb start
+install:
+ - npm install
+script:
+ - npm run lint
+ - npm run test-once
+ - npm run e2e
diff --git a/frontend/manage/LICENSE-angular-quickstart b/frontend/manage/LICENSE-angular-quickstart
new file mode 100644
index 00000000..51b127e8
--- /dev/null
+++ b/frontend/manage/LICENSE-angular-quickstart
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2014-2016 Google, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/frontend/manage/README.md b/frontend/manage/README.md
new file mode 100644
index 00000000..4b8baf4d
--- /dev/null
+++ b/frontend/manage/README.md
@@ -0,0 +1,101 @@
+# see Angular QuickStart Source (github)
+
+## Prerequisites
+
+Node.js and npm are essential to Angular development.
+
+
+Get it now if it's not already installed on your machine.
+
+**Verify that you are running at least node `v4.x.x` and npm `3.x.x`**
+by running `node -v` and `npm -v` in a terminal/console window.
+Older versions produce errors.
+
+We recommend [nvm](https://github.com/creationix/nvm) for managing multiple versions of node and npm.
+
+## Install npm packages
+
+> See npm and nvm version notes above
+
+Install the npm packages described in the `package.json` and verify that it works:
+
+```bash
+npm install
+npm start
+```
+
+Only for development: (This angular project should be served by staticserver (go project))
+
+The `npm start` command first compiles the application,
+then simultaneously re-compiles and runs the `lite-server`.
+Both the compiler and the server watch for file changes.
+
+Shut it down manually with `Ctrl-C`.
+Index.html contains angular test lcp project
+The old index.html is now renamed to manage.html
+
+### npm scripts
+
+We've captured many of the most useful commands in npm scripts defined in the `package.json`:
+
+* `npm start` - runs the compiler and a server at the same time, both in "watch mode".
+* `npm run tsc` - runs the TypeScript compiler once.
+* `npm run tsc:w` - runs the TypeScript compiler in watch mode; the process keeps running, awaiting changes to TypeScript files and re-compiling when it sees them.
+* `npm run lite` - runs the [lite-server](https://www.npmjs.com/package/lite-server), a light-weight, static file server, written and maintained by
+[John Papa](https://github.com/johnpapa) and
+[Christopher Martin](https://github.com/cgmartin)
+with excellent support for Angular apps that use routing.
+
+Here are the test related scripts:
+* `npm test` - compiles, runs and watches the karma unit tests
+* `npm run e2e` - run protractor e2e tests, written in JavaScript (*e2e-spec.js)
+
+## Testing
+
+karma/jasmine unit test and protractor end-to-end testing support.
+
+These tools are configured for specific conventions described below.
+
+*It is unwise and rarely possible to run the application, the unit tests, and the e2e tests at the same time.
+We recommend that you shut down one before starting another.*
+
+### Unit Tests
+TypeScript unit-tests are usually in the `app` folder. Their filenames must end in `.spec`.
+
+Look for the example `app/app.component.spec.ts`.
+Add more `.spec.ts` files as you wish; we configured karma to find them.
+
+Run it with `npm test`
+
+That command first compiles the application, then simultaneously re-compiles and runs the karma test-runner.
+Both the compiler and the karma watch for (different) file changes.
+
+Shut it down manually with `Ctrl-C`.
+
+Test-runner output appears in the terminal window.
+We can update our app and our tests in real-time, keeping a weather eye on the console for broken tests.
+Karma is occasionally confused and it is often necessary to shut down its browser or even shut the command down (`Ctrl-C`) and
+restart it. No worries; it's pretty quick.
+
+### End-to-end (E2E) Tests
+
+E2E tests are in the `e2e` directory, side by side with the `app` folder.
+Their filenames must end in `.e2e-spec.ts`.
+
+Look for the example `e2e/app.e2e-spec.ts`.
+Add more `.e2e-spec.js` files as you wish (although one usually suffices for small projects);
+we configured protractor to find them.
+
+Thereafter, run them with `npm run e2e`.
+
+That command first compiles, then simultaneously starts the Http-Server at `localhost:8080`
+and launches protractor.
+
+The pass/fail test results appear at the bottom of the terminal window.
+A custom reporter (see `protractor.config.js`) generates a `./_test-output/protractor-results.txt` file
+which is easier to read; this file is excluded from source control.
+
+Shut it down manually with `Ctrl-C`.
+
+[travis-badge]: https://travis-ci.org/angular/quickstart.svg?branch=master
+[travis-badge-url]: https://travis-ci.org/angular/quickstart
diff --git a/frontend/manage/app/app-routing.module.ts b/frontend/manage/app/app-routing.module.ts
new file mode 100644
index 00000000..d2ab1e3f
--- /dev/null
+++ b/frontend/manage/app/app-routing.module.ts
@@ -0,0 +1,19 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { PageNotFoundComponent } from './not-found.component';
+
+const appRoutes: Routes = [
+ { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
+ { path: '**', component: PageNotFoundComponent }
+];
+
+@NgModule({
+ imports: [
+ RouterModule.forRoot(appRoutes)
+ ],
+ exports: [
+ RouterModule
+ ]
+})
+
+export class AppRoutingModule {}
diff --git a/frontend/manage/app/app.component.html b/frontend/manage/app/app.component.html
new file mode 100644
index 00000000..5b7fbdf9
--- /dev/null
+++ b/frontend/manage/app/app.component.html
@@ -0,0 +1,13 @@
+