github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/lcpserver/api/license.go (about) 1 // Copyright 2017 Readium Foundation. All rights reserved. 2 // Use of this source code is governed by a BSD-style license 3 // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 4 5 package apilcp 6 7 import ( 8 "archive/zip" 9 "bytes" 10 "encoding/hex" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "io" 15 "log" 16 "net/http" 17 "strconv" 18 "time" 19 20 "github.com/gorilla/mux" 21 22 "github.com/readium/readium-lcp-server/api" 23 "github.com/readium/readium-lcp-server/config" 24 "github.com/readium/readium-lcp-server/epub" 25 "github.com/readium/readium-lcp-server/index" 26 "github.com/readium/readium-lcp-server/license" 27 "github.com/readium/readium-lcp-server/logging" 28 "github.com/readium/readium-lcp-server/problem" 29 "github.com/readium/readium-lcp-server/storage" 30 ) 31 32 // ErrMandatoryInfoMissing sets an error message returned to the caller 33 var ErrMandatoryInfoMissing = errors.New("mandatory info missing in the input body") 34 35 // ErrBadHexValue sets an error message returned to the caller 36 var ErrBadHexValue = errors.New("erroneous user_key.hex_value can't be decoded") 37 38 // ErrBadValue sets an error message returned to the caller 39 var ErrBadValue = errors.New("erroneous user_key.value, can't be decoded") 40 41 // checkGetLicenseInput: if we generate or get a license, check mandatory information in the input body 42 // and compute request parameters 43 func checkGetLicenseInput(l *license.License) error { 44 45 // the user hint is mandatory 46 if l.Encryption.UserKey.Hint == "" { 47 log.Println("User hint is missing") 48 return ErrMandatoryInfoMissing 49 } 50 // Value or HexValue are mandatory 51 // HexValue (hex encoded passphrase hash) takes precedence over Value (kept for backward compatibility) 52 // Value is computed from HexValue if set 53 if l.Encryption.UserKey.HexValue != "" { 54 // compute a byte array from a string 55 value, err := hex.DecodeString(l.Encryption.UserKey.HexValue) 56 if err != nil { 57 return ErrBadHexValue 58 } 59 l.Encryption.UserKey.Value = value 60 } else if l.Encryption.UserKey.Value == nil { 61 log.Println("User hashed passphrase is missing") 62 return ErrMandatoryInfoMissing 63 } 64 // check the size of Value (32 bytes), to avoid weird errors in the crypto code 65 if len(l.Encryption.UserKey.Value) != 32 { 66 return ErrBadValue 67 } 68 69 return nil 70 } 71 72 // checkGenerateLicenseInput: if we generate a license, check mandatory information in the input body 73 func checkGenerateLicenseInput(l *license.License) error { 74 75 if l.User.ID == "" { 76 log.Println("User identification is missing") 77 return ErrMandatoryInfoMissing 78 } 79 // check user hint, passphrase hash and hash algorithm 80 err := checkGetLicenseInput(l) 81 return err 82 } 83 84 // get license, copy useful data from licIn to LicOut 85 func copyInputToLicense(licIn *license.License, licOut *license.License) { 86 87 // copy the user hint and hashed passphrase 88 licOut.Encryption.UserKey.Hint = licIn.Encryption.UserKey.Hint 89 licOut.Encryption.UserKey.Value = licIn.Encryption.UserKey.Value 90 licOut.Encryption.UserKey.HexValue = licIn.Encryption.UserKey.HexValue 91 92 // copy optional user information 93 licOut.User.Email = licIn.User.Email 94 licOut.User.Name = licIn.User.Name 95 licOut.User.Encrypted = licIn.User.Encrypted 96 licOut.Links = licIn.Links 97 } 98 99 // normalize the start and end date, UTC, no milliseconds 100 func setRights(lic *license.License) { 101 102 // a rights object is needed before adding a record to the db 103 if lic.Rights == nil { 104 lic.Rights = new(license.UserRights) 105 } 106 if lic.Rights.Start != nil { 107 start := lic.Rights.Start.UTC().Truncate(time.Second) 108 lic.Rights.Start = &start 109 } 110 if lic.Rights.End != nil { 111 end := lic.Rights.End.UTC().Truncate(time.Second) 112 lic.Rights.End = &end 113 } 114 } 115 116 // build a license, common to get and generate license, get and generate licensed publication 117 func buildLicense(lic *license.License, s Server, updatefix bool) error { 118 119 // set the LCP profile 120 err := license.SetLicenseProfile(lic) 121 if err != nil { 122 log.Println("Build License: " + err.Error()) 123 return err 124 } 125 126 // force the algorithm to the one defined by the current profiles 127 lic.Encryption.UserKey.Algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" 128 129 // get content info from the db 130 content, err := s.Index().Get(lic.ContentID) 131 if err != nil { 132 log.Println("No content with id", lic.ContentID) 133 return err 134 } 135 136 // set links 137 err = license.SetLicenseLinks(lic, content) 138 if err != nil { 139 return err 140 } 141 // encrypt the content key, user fieds, set the key check 142 err = license.EncryptLicenseFields(lic, content) 143 if err != nil { 144 return err 145 } 146 147 // fix an issue with clients which test that the date of last update of the license 148 // is after the date of creation of the X509 certificate. 149 // Because of this, when a cert is replaced, fresh licenses are not accepted by such clients 150 // when they have been created / updated before the cert update. 151 if updatefix && config.Config.LcpServer.CertDate != "" { 152 certDate, err := time.Parse("2006-01-02", config.Config.LcpServer.CertDate) 153 if err != nil { 154 return err 155 } 156 if lic.Issued.Before(certDate) && (lic.Updated == nil || lic.Updated.Before(certDate)) { 157 lic.Updated = &certDate 158 } 159 } 160 161 // sign the license 162 err = license.SignLicense(lic, s.Certificate()) 163 if err != nil { 164 return err 165 } 166 return nil 167 } 168 169 // copyZipFiles copies every file from one zip archive to another 170 func copyZipFiles(out *zip.Writer, in *zip.Reader) error { 171 172 for _, file := range in.File { 173 newFile, err := out.CreateHeader(&file.FileHeader) 174 if err != nil { 175 return err 176 } 177 178 r, err := file.Open() 179 if err != nil { 180 return err 181 } 182 183 _, err = io.Copy(newFile, r) 184 r.Close() 185 if err != nil { 186 return err 187 } 188 } 189 190 return nil 191 } 192 193 // isWebPub checks the presence of a REadium manifest is a zip package 194 func isWebPub(in *zip.Reader) bool { 195 196 for _, f := range in.File { 197 if f.Name == "manifest.json" { 198 return true 199 } 200 } 201 202 return false 203 } 204 205 // buildLicensedPublication builds a licensed publication, common to get and generate licensed publication 206 func buildLicensedPublication(lic *license.License, s Server) (buf bytes.Buffer, err error) { 207 208 // get content info from the bd 209 item, err := s.Store().Get(lic.ContentID) 210 if err != nil { 211 return 212 } 213 // read the content into a buffer 214 contents, err := item.Contents() 215 if err != nil { 216 return buf, err 217 } 218 b, err := io.ReadAll(contents) 219 if err != nil { 220 return buf, err 221 } 222 // create a zip reader 223 zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) 224 if err != nil { 225 return buf, err 226 } 227 228 zipWriter := zip.NewWriter(&buf) 229 err = copyZipFiles(zipWriter, zr) 230 if err != nil { 231 return buf, err 232 } 233 234 // Encode the license to JSON, remove the trailing newline 235 // write the buffer in the zip 236 licenseBytes, err := json.Marshal(lic) 237 if err != nil { 238 return buf, err 239 } 240 241 licenseBytes = bytes.TrimRight(licenseBytes, "\n") 242 243 location := epub.LicenseFile 244 if isWebPub(zr) { 245 location = "license.lcpl" 246 } 247 248 licenseWriter, err := zipWriter.Create(location) 249 if err != nil { 250 return buf, err 251 } 252 253 _, err = licenseWriter.Write(licenseBytes) 254 if err != nil { 255 return 256 } 257 258 return buf, zipWriter.Close() 259 } 260 261 // GetLicense returns an existing license, 262 // selected by a license id and a partial license both given as input. 263 // The input partial license is optional: if absent, a partial license 264 // is returned to the caller, with the info stored in the db. 265 func GetLicense(w http.ResponseWriter, r *http.Request, s Server) { 266 267 vars := mux.Vars(r) 268 // get the license id from the request URL 269 licenseID := vars["license_id"] 270 271 // add a log 272 logging.Print("Get the License " + licenseID) 273 274 // initialize the license from the info stored in the db. 275 var licOut license.License 276 licOut, e := s.Licenses().Get(licenseID) 277 // process license not found etc. 278 if e == license.ErrNotFound { 279 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusNotFound) 280 return 281 } else if e != nil { 282 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusBadRequest) 283 return 284 } 285 // get the input body. 286 // It contains the hashed passphrase, user hint 287 // and other optional user data the provider wants to see embedded in the license 288 var err error 289 var licIn license.License 290 err = DecodeJSONLicense(r, &licIn) 291 // error parsing the input body 292 if err != nil { 293 // if there was no partial license given as payload, return a partial license. 294 // The use case is a frontend that needs to get license up to date rights. 295 if err.Error() == "EOF" { 296 log.Println("No payload, get a partial license") 297 298 // add useful http headers 299 w.Header().Add("Content-Type", api.ContentType_LCP_JSON) 300 w.WriteHeader(http.StatusPartialContent) 301 // send back the partial license 302 // do not escape characters 303 enc := json.NewEncoder(w) 304 enc.SetEscapeHTML(false) 305 enc.Encode(licOut) 306 return 307 } 308 // unknown error 309 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 310 return 311 } 312 313 // an input body was sent with the request: 314 // check mandatory information in the partial license 315 err = checkGetLicenseInput(&licIn) 316 if err != nil { 317 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 318 return 319 } 320 // copy useful data from licIn to LicOut 321 copyInputToLicense(&licIn, &licOut) 322 // build the license 323 err = buildLicense(&licOut, s, true) 324 if err != nil { 325 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 326 return 327 } 328 329 // set the http headers 330 w.Header().Add("Content-Type", api.ContentType_LCP_JSON) 331 w.Header().Add("Content-Disposition", `attachment; filename="license.lcpl"`) 332 w.WriteHeader(http.StatusOK) 333 // send back the license 334 // do not escape characters in the json payload 335 enc := json.NewEncoder(w) 336 enc.SetEscapeHTML(false) 337 enc.Encode(licOut) 338 } 339 340 // GenerateLicense generates and returns a new license, 341 // for a given content identified by its id 342 // plus a partial license given as input 343 func GenerateLicense(w http.ResponseWriter, r *http.Request, s Server) { 344 345 vars := mux.Vars(r) 346 // get the content id from the request URL 347 contentID := vars["content_id"] 348 349 // get the input body 350 // note: no need to create licIn / licOut here, as the input body contains 351 // info that we want to keep in the full license. 352 var lic license.License 353 err := DecodeJSONLicense(r, &lic) 354 if err != nil { 355 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 356 return 357 } 358 // check mandatory information in the input body 359 err = checkGenerateLicenseInput(&lic) 360 if err != nil { 361 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 362 return 363 } 364 // init the license with an id and issue date 365 license.Initialize(contentID, &lic) 366 367 // normalize the start and end date, UTC, no milliseconds 368 setRights(&lic) 369 370 // build the license 371 err = buildLicense(&lic, s, false) 372 if err != nil { 373 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 374 return 375 } 376 377 // store the license in the db 378 err = s.Licenses().Add(lic) 379 if err != nil { 380 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 381 //problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: contentID}, http.StatusInternalServerError) 382 return 383 } 384 385 // add a log 386 logging.Print("Generate a License " + lic.ID + " for Content " + contentID + " and User " + lic.User.ID) 387 388 // set http headers 389 w.Header().Add("Content-Type", api.ContentType_LCP_JSON) 390 w.Header().Add("Content-Disposition", `attachment; filename="license.lcpl"`) 391 w.WriteHeader(http.StatusCreated) 392 // send back the license 393 // do not escape characters 394 enc := json.NewEncoder(w) 395 enc.SetEscapeHTML(false) 396 enc.Encode(lic) 397 398 // notify the lsd server of the creation of the license. 399 // this is an asynchronous call. 400 go notifyLsdServer(lic, s) 401 } 402 403 // GetLicensedPublication returns a licensed publication 404 // for a given license identified by its id 405 // plus a partial license given as input 406 func GetLicensedPublication(w http.ResponseWriter, r *http.Request, s Server) { 407 408 vars := mux.Vars(r) 409 licenseID := vars["license_id"] 410 411 // add a log 412 logging.Print("Get a Licensed publication for License " + licenseID) 413 414 // get the input body 415 var licIn license.License 416 err := DecodeJSONLicense(r, &licIn) 417 if err != nil { 418 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 419 return 420 } 421 // check mandatory information in the input body 422 err = checkGetLicenseInput(&licIn) 423 if err != nil { 424 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 425 return 426 } 427 // initialize the license from the info stored in the db. 428 licOut, e := s.Licenses().Get(licenseID) 429 // process license not found etc. 430 if e == license.ErrNotFound { 431 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusNotFound) 432 return 433 } else if e != nil { 434 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusBadRequest) 435 return 436 } 437 // copy useful data from licIn to LicOut 438 copyInputToLicense(&licIn, &licOut) 439 // build the license 440 err = buildLicense(&licOut, s, true) 441 if err != nil { 442 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 443 return 444 } 445 // build a licensed publication 446 buf, err := buildLicensedPublication(&licOut, s) 447 if err == storage.ErrNotFound { 448 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: licOut.ContentID}, http.StatusNotFound) 449 return 450 } else if err != nil { 451 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: licOut.ContentID}, http.StatusInternalServerError) 452 return 453 } 454 // get the content location to fill an http header 455 // FIXME: redundant as the content location has been set in a link (publication) 456 content, err1 := s.Index().Get(licOut.ContentID) 457 if err1 != nil { 458 problem.Error(w, r, problem.Problem{Detail: err1.Error(), Instance: licOut.ContentID}, http.StatusInternalServerError) 459 return 460 } 461 location := content.Location 462 463 // set HTTP headers 464 w.Header().Add("Content-Type", epub.ContentType_EPUB) 465 w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, location)) 466 // FIXME: check the use of X-Lcp-License by the caller (frontend?) 467 w.Header().Add("X-Lcp-License", licOut.ID) 468 // must come *after* w.Header().Add()/Set(), but before w.Write() 469 w.WriteHeader(http.StatusCreated) 470 // return the full licensed publication to the caller 471 io.Copy(w, &buf) 472 } 473 474 // GenerateLicensedPublication generates and returns a licensed publication 475 // for a given content identified by its id 476 // plus a partial license given as input 477 func GenerateLicensedPublication(w http.ResponseWriter, r *http.Request, s Server) { 478 479 vars := mux.Vars(r) 480 contentID := vars["content_id"] 481 482 logging.Print("Generate a Licensed publication for Content " + contentID) 483 484 // get the input body 485 var lic license.License 486 err := DecodeJSONLicense(r, &lic) 487 if err != nil { 488 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 489 return 490 } 491 // check mandatory information in the input body 492 err = checkGenerateLicenseInput(&lic) 493 if err != nil { 494 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 495 return 496 } 497 // init the license with an id and issue date 498 license.Initialize(contentID, &lic) 499 // normalize the start and end date, UTC, no milliseconds 500 setRights(&lic) 501 502 // build the license 503 err = buildLicense(&lic, s, false) 504 if err != nil { 505 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 506 return 507 } 508 // store the license in the db 509 err = s.Licenses().Add(lic) 510 if err != nil { 511 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: contentID}, http.StatusInternalServerError) 512 return 513 } 514 515 // notify the lsd server of the creation of the license 516 go notifyLsdServer(lic, s) 517 518 // build a licenced publication 519 buf, err := buildLicensedPublication(&lic, s) 520 if err == storage.ErrNotFound { 521 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: lic.ContentID}, http.StatusNotFound) 522 return 523 } else if err != nil { 524 problem.Error(w, r, problem.Problem{Detail: err.Error(), Instance: lic.ContentID}, http.StatusInternalServerError) 525 return 526 } 527 528 // get the content location to fill an http header 529 // FIXME: redundant as the content location has been set in a link (publication) 530 content, err1 := s.Index().Get(lic.ContentID) 531 if err1 != nil { 532 problem.Error(w, r, problem.Problem{Detail: err1.Error(), Instance: lic.ContentID}, http.StatusInternalServerError) 533 return 534 } 535 location := content.Location 536 537 // set HTTP headers 538 w.Header().Add("Content-Type", epub.ContentType_EPUB) 539 w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, location)) 540 // FIXME: check the use of X-Lcp-License by the caller (frontend?) 541 w.Header().Add("X-Lcp-License", lic.ID) 542 // must come *after* w.Header().Add()/Set(), but before w.Write() 543 w.WriteHeader(http.StatusCreated) 544 // return the full licensed publication to the caller 545 io.Copy(w, &buf) 546 } 547 548 // UpdateLicense updates an existing license. 549 // parameters: 550 // 551 // {license_id} in the calling URL 552 // partial license containing properties which should be updated (and only these) 553 // 554 // return: an http status code (200, 400 or 404) 555 // Usually called from the License Status Server after a renew, return or cancel/revoke action 556 // -> updates the end date. 557 func UpdateLicense(w http.ResponseWriter, r *http.Request, s Server) { 558 559 vars := mux.Vars(r) 560 // get the license id from the request URL 561 licenseID := vars["license_id"] 562 563 // add a log 564 logging.Print("Update the License " + licenseID) 565 566 var licIn license.License 567 err := DecodeJSONLicense(r, &licIn) 568 if err != nil { // no or incorrect (json) partial license found in the body 569 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 570 return 571 } 572 // initialize the license from the info stored in the db. 573 var licOut license.License 574 licOut, e := s.Licenses().Get(licenseID) 575 // process license not found etc. 576 if e == license.ErrNotFound { 577 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusNotFound) 578 return 579 } else if e != nil { 580 problem.Error(w, r, problem.Problem{Detail: e.Error()}, http.StatusBadRequest) 581 return 582 } 583 // update licOut using information found in licIn 584 if licIn.User.ID != "" { 585 log.Println("new user id: ", licIn.User.ID) 586 licOut.User.ID = licIn.User.ID 587 } 588 if licIn.Provider != "" { 589 log.Println("new provider: ", licIn.Provider) 590 licOut.Provider = licIn.Provider 591 } 592 if licIn.ContentID != "" { 593 log.Println("new content id: ", licIn.ContentID) 594 licOut.ContentID = licIn.ContentID 595 } 596 if licIn.Rights.Print != nil { 597 log.Println("new right, print: ", *licIn.Rights.Print) 598 licOut.Rights.Print = licIn.Rights.Print 599 } 600 if licIn.Rights.Copy != nil { 601 log.Println("new right, copy: ", *licIn.Rights.Copy) 602 licOut.Rights.Copy = licIn.Rights.Copy 603 } 604 if licIn.Rights.Start != nil { 605 log.Println("new right, start: ", *licIn.Rights.Start) 606 licOut.Rights.Start = licIn.Rights.Start 607 } 608 if licIn.Rights.End != nil { 609 log.Println("new right, end: ", *licIn.Rights.End) 610 licOut.Rights.End = licIn.Rights.End 611 } 612 // update the license in the database 613 err = s.Licenses().Update(licOut) 614 if err != nil { 615 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusInternalServerError) 616 return 617 } 618 } 619 620 // ListLicenses returns a JSON struct with information about the existing licenses 621 // parameters: 622 // 623 // page: page number 624 // per_page: number of items par page 625 func ListLicenses(w http.ResponseWriter, r *http.Request, s Server) { 626 627 var page int64 628 var perPage int64 629 var err error 630 if r.FormValue("page") != "" { 631 page, err = strconv.ParseInt((r).FormValue("page"), 10, 32) 632 if err != nil { 633 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 634 return 635 } 636 } else { 637 page = 1 638 } 639 if r.FormValue("per_page") != "" { 640 perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32) 641 if err != nil { 642 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 643 return 644 } 645 } else { 646 perPage = 30 647 } 648 if page > 0 { //pagenum starting at 0 in code, but user interface starting at 1 649 page-- 650 } 651 if page < 0 { 652 problem.Error(w, r, problem.Problem{Detail: "page must be positive integer"}, http.StatusBadRequest) 653 return 654 } 655 licenses := make([]license.LicenseReport, 0) 656 657 // add a log 658 logging.Print("List Licenses (page " + strconv.Itoa(int(page)) + ", count " + strconv.Itoa(int(perPage)) + ")") 659 660 fn := s.Licenses().ListAll(int(perPage), int(page)) 661 for it, err := fn(); err == nil; it, err = fn() { 662 licenses = append(licenses, it) 663 } 664 if len(licenses) > 0 { 665 nextPage := strconv.Itoa(int(page) + 1) 666 w.Header().Set("Link", "</licenses/?page="+nextPage+">; rel=\"next\"; title=\"next\"") 667 } 668 if page > 1 { 669 previousPage := strconv.Itoa(int(page) - 1) 670 w.Header().Set("Link", "</licenses/?page="+previousPage+">; rel=\"previous\"; title=\"previous\"") 671 } 672 w.Header().Set("Content-Type", api.ContentType_JSON) 673 674 enc := json.NewEncoder(w) 675 // do not escape characters 676 enc.SetEscapeHTML(false) 677 err = enc.Encode(licenses) 678 if err != nil { 679 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 680 return 681 } 682 } 683 684 // ListLicensesForContent lists all licenses associated with a given content 685 // parameters: 686 // 687 // content_id: content identifier 688 // page: page number (default 1) 689 // per_page: number of items par page (default 30) 690 func ListLicensesForContent(w http.ResponseWriter, r *http.Request, s Server) { 691 692 vars := mux.Vars(r) 693 var page int64 694 var perPage int64 695 var err error 696 contentID := vars["content_id"] 697 698 //check if the license exists 699 _, err = s.Index().Get(contentID) 700 if err == index.ErrNotFound { 701 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusNotFound) 702 return 703 } //other errors pass, but will probably reoccur 704 if r.FormValue("page") != "" { 705 page, err = strconv.ParseInt(r.FormValue("page"), 10, 32) 706 if err != nil { 707 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 708 return 709 } 710 } else { 711 page = 1 712 } 713 714 if r.FormValue("per_page") != "" { 715 perPage, err = strconv.ParseInt((r).FormValue("per_page"), 10, 32) 716 if err != nil { 717 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 718 return 719 } 720 } else { 721 perPage = 30 722 } 723 if page > 0 { 724 page-- //pagenum starting at 0 in code, but user interface starting at 1 725 } 726 if page < 0 { 727 problem.Error(w, r, problem.Problem{Detail: "page must be positive integer"}, http.StatusBadRequest) 728 return 729 } 730 licenses := make([]license.LicenseReport, 0) 731 732 // add a log 733 logging.Print("List Licenses for publication " + contentID + " (page " + strconv.Itoa(int(page)) + ", count " + strconv.Itoa(int(perPage)) + ")") 734 735 fn := s.Licenses().ListByContentID(contentID, int(perPage), int(page)) 736 for it, err := fn(); err == nil; it, err = fn() { 737 licenses = append(licenses, it) 738 } 739 if len(licenses) > 0 { 740 nextPage := strconv.Itoa(int(page) + 1) 741 w.Header().Set("Link", "</licenses/?page="+nextPage+">; rel=\"next\"; title=\"next\"") 742 } 743 if page > 1 { 744 previousPage := strconv.Itoa(int(page) - 1) 745 w.Header().Set("Link", "</licenses/?page="+previousPage+">; rel=\"previous\"; title=\"previous\"") 746 } 747 w.Header().Set("Content-Type", api.ContentType_JSON) 748 enc := json.NewEncoder(w) 749 // do not escape characters 750 enc.SetEscapeHTML(false) 751 err = enc.Encode(licenses) 752 if err != nil { 753 problem.Error(w, r, problem.Problem{Detail: err.Error()}, http.StatusBadRequest) 754 return 755 } 756 757 } 758 759 // DecodeJSONLicense decodes a license formatted in json and returns a license object 760 func DecodeJSONLicense(r *http.Request, lic *license.License) error { 761 762 var dec *json.Decoder 763 764 if ctype := r.Header["Content-Type"]; len(ctype) > 0 && ctype[0] == api.ContentType_FORM_URL_ENCODED { 765 buf := bytes.NewBufferString(r.PostFormValue("data")) 766 dec = json.NewDecoder(buf) 767 } else { 768 dec = json.NewDecoder(r.Body) 769 } 770 771 err := dec.Decode(&lic) 772 773 if err != nil && err.Error() != "EOF" { 774 log.Print("Decode license: invalid json structure") 775 } 776 return err 777 } 778 779 // notifyLsdServer informs the License Status Server of the creation of a new license 780 // and saves the result of the http request in the DB (using *Store) 781 func notifyLsdServer(l license.License, s Server) { 782 783 if config.Config.LsdServer.PublicBaseUrl != "" { 784 var lsdClient = &http.Client{ 785 Timeout: time.Second * 10, 786 } 787 pr, pw := io.Pipe() 788 defer pr.Close() 789 go func() { 790 _ = json.NewEncoder(pw).Encode(l) 791 pw.Close() // signal end writing 792 }() 793 req, err := http.NewRequest("PUT", config.Config.LsdServer.PublicBaseUrl+"/licenses", pr) 794 if err != nil { 795 return 796 } 797 // set credentials on lsd request 798 notifyAuth := config.Config.LsdNotifyAuth 799 if notifyAuth.Username != "" { 800 req.SetBasicAuth(notifyAuth.Username, notifyAuth.Password) 801 } 802 803 req.Header.Add("Content-Type", api.ContentType_LCP_JSON) 804 805 response, err := lsdClient.Do(req) 806 if err != nil { 807 log.Println("Error Notify LsdServer of new License (" + l.ID + "):" + err.Error()) 808 _ = s.Licenses().UpdateLsdStatus(l.ID, -1) 809 } else { 810 defer req.Body.Close() 811 _ = s.Licenses().UpdateLsdStatus(l.ID, int32(response.StatusCode)) 812 } 813 } 814 }