github.com/decred/politeia@v1.4.0/politeiad/v2.go (about) 1 // Copyright (c) 2020-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 package main 5 6 import ( 7 "encoding/hex" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "net/http" 12 "runtime/debug" 13 "time" 14 15 v2 "github.com/decred/politeia/politeiad/api/v2" 16 "github.com/decred/politeia/politeiad/backendv2" 17 "github.com/decred/politeia/util" 18 ) 19 20 func (p *politeia) handleRecordNew(w http.ResponseWriter, r *http.Request) { 21 log.Tracef("handleRecordNew") 22 23 // Decode request 24 var rn v2.RecordNew 25 decoder := json.NewDecoder(r.Body) 26 if err := decoder.Decode(&rn); err != nil { 27 respondWithErrorV2(w, r, "handleRecordNew: unmarshal", 28 v2.UserErrorReply{ 29 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 30 }) 31 return 32 } 33 challenge, err := hex.DecodeString(rn.Challenge) 34 if err != nil || len(challenge) != v2.ChallengeSize { 35 respondWithErrorV2(w, r, "handleRecordNew: decode challenge", 36 v2.UserErrorReply{ 37 ErrorCode: v2.ErrorCodeChallengeInvalid, 38 }) 39 return 40 } 41 42 // Create new record 43 var ( 44 metadata = convertMetadataStreamsToBackend(rn.Metadata) 45 files = convertFilesToBackend(rn.Files) 46 ) 47 rc, err := p.backendv2.RecordNew(metadata, files) 48 if err != nil { 49 respondWithErrorV2(w, r, 50 "handleRecordNew: RecordNew: %v", err) 51 return 52 } 53 54 // Prepare reply 55 response := p.identity.SignMessage(challenge) 56 rnr := v2.RecordNewReply{ 57 Response: hex.EncodeToString(response[:]), 58 Record: p.convertRecordToV2(*rc), 59 } 60 61 log.Infof("%v Record created %v", 62 util.RemoteAddr(r), rc.RecordMetadata.Token) 63 64 util.RespondWithJSON(w, http.StatusOK, rnr) 65 } 66 67 func (p *politeia) handleRecordEdit(w http.ResponseWriter, r *http.Request) { 68 log.Tracef("handleRecordEdit") 69 70 // Decode request 71 var re v2.RecordEdit 72 decoder := json.NewDecoder(r.Body) 73 if err := decoder.Decode(&re); err != nil { 74 respondWithErrorV2(w, r, "handleRecordEdit: unmarshal", 75 v2.UserErrorReply{ 76 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 77 }) 78 return 79 } 80 challenge, err := hex.DecodeString(re.Challenge) 81 if err != nil || len(challenge) != v2.ChallengeSize { 82 respondWithErrorV2(w, r, "handleRecordEdit: decode challenge", 83 v2.UserErrorReply{ 84 ErrorCode: v2.ErrorCodeChallengeInvalid, 85 }) 86 return 87 } 88 token, err := decodeToken(re.Token) 89 if err != nil { 90 respondWithErrorV2(w, r, "handleRecordEdit: decode token", 91 v2.UserErrorReply{ 92 ErrorCode: v2.ErrorCodeTokenInvalid, 93 ErrorContext: util.TokenRegexp(), 94 }) 95 return 96 } 97 98 // Edit record 99 var ( 100 mdAppend = convertMetadataStreamsToBackend(re.MDAppend) 101 mdOverwrite = convertMetadataStreamsToBackend(re.MDOverwrite) 102 filesAdd = convertFilesToBackend(re.FilesAdd) 103 ) 104 rc, err := p.backendv2.RecordEdit(token, mdAppend, 105 mdOverwrite, filesAdd, re.FilesDel) 106 if err != nil { 107 respondWithErrorV2(w, r, 108 "handleRecordEdit: RecordEdit: %v", err) 109 return 110 } 111 112 // Prepare reply 113 response := p.identity.SignMessage(challenge) 114 rer := v2.RecordEditReply{ 115 Response: hex.EncodeToString(response[:]), 116 Record: p.convertRecordToV2(*rc), 117 } 118 119 log.Infof("%v Record edited %v", 120 util.RemoteAddr(r), rc.RecordMetadata.Token) 121 122 util.RespondWithJSON(w, http.StatusOK, rer) 123 } 124 125 func (p *politeia) handleRecordEditMetadata(w http.ResponseWriter, r *http.Request) { 126 log.Tracef("handleRecordEditMetadata") 127 128 // Decode request 129 var re v2.RecordEditMetadata 130 decoder := json.NewDecoder(r.Body) 131 if err := decoder.Decode(&re); err != nil { 132 respondWithErrorV2(w, r, "handleRecordEditMetadata: unmarshal", 133 v2.UserErrorReply{ 134 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 135 }) 136 return 137 } 138 challenge, err := hex.DecodeString(re.Challenge) 139 if err != nil || len(challenge) != v2.ChallengeSize { 140 respondWithErrorV2(w, r, "handleRecordEditMetadata: decode challenge", 141 v2.UserErrorReply{ 142 ErrorCode: v2.ErrorCodeChallengeInvalid, 143 }) 144 return 145 } 146 token, err := decodeToken(re.Token) 147 if err != nil { 148 respondWithErrorV2(w, r, "handleRecordEditMetadata: decode token", 149 v2.UserErrorReply{ 150 ErrorCode: v2.ErrorCodeTokenInvalid, 151 ErrorContext: util.TokenRegexp(), 152 }) 153 return 154 } 155 156 // Edit record metadata 157 var ( 158 mdAppend = convertMetadataStreamsToBackend(re.MDAppend) 159 mdOverwrite = convertMetadataStreamsToBackend(re.MDOverwrite) 160 ) 161 rc, err := p.backendv2.RecordEditMetadata(token, mdAppend, mdOverwrite) 162 if err != nil { 163 respondWithErrorV2(w, r, 164 "handleRecordEditMetadata: RecordEditMetadata: %v", err) 165 return 166 } 167 168 // Prepare reply 169 response := p.identity.SignMessage(challenge) 170 rer := v2.RecordEditMetadataReply{ 171 Response: hex.EncodeToString(response[:]), 172 Record: p.convertRecordToV2(*rc), 173 } 174 175 util.RespondWithJSON(w, http.StatusOK, rer) 176 } 177 178 func (p *politeia) handleRecordSetStatus(w http.ResponseWriter, r *http.Request) { 179 log.Tracef("handleRecordSetStatus") 180 181 // Decode request 182 var rss v2.RecordSetStatus 183 decoder := json.NewDecoder(r.Body) 184 if err := decoder.Decode(&rss); err != nil { 185 respondWithErrorV2(w, r, "handleRecordSetStatus: unmarshal", 186 v2.UserErrorReply{ 187 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 188 }) 189 return 190 } 191 challenge, err := hex.DecodeString(rss.Challenge) 192 if err != nil || len(challenge) != v2.ChallengeSize { 193 respondWithErrorV2(w, r, "handleRecordSetStatus: decode challenge", 194 v2.UserErrorReply{ 195 ErrorCode: v2.ErrorCodeChallengeInvalid, 196 }) 197 return 198 } 199 token, err := decodeToken(rss.Token) 200 if err != nil { 201 respondWithErrorV2(w, r, "handleRecordSetStatus: decode token", 202 v2.UserErrorReply{ 203 ErrorCode: v2.ErrorCodeTokenInvalid, 204 ErrorContext: util.TokenRegexp(), 205 }) 206 return 207 } 208 209 // Set record status 210 var ( 211 mdAppend = convertMetadataStreamsToBackend(rss.MDAppend) 212 mdOverwrite = convertMetadataStreamsToBackend(rss.MDOverwrite) 213 status = backendv2.StatusT(rss.Status) 214 ) 215 rc, err := p.backendv2.RecordSetStatus(token, status, 216 mdAppend, mdOverwrite) 217 if err != nil { 218 respondWithErrorV2(w, r, 219 "handleRecordSetStatus: RecordSetStatus: %v", err) 220 return 221 } 222 223 // Prepare reply 224 response := p.identity.SignMessage(challenge) 225 rer := v2.RecordSetStatusReply{ 226 Response: hex.EncodeToString(response[:]), 227 Record: p.convertRecordToV2(*rc), 228 } 229 230 log.Infof("%v Record status set %v %v", util.RemoteAddr(r), 231 rc.RecordMetadata.Token, backendv2.Statuses[rc.RecordMetadata.Status]) 232 233 util.RespondWithJSON(w, http.StatusOK, rer) 234 } 235 236 func (p *politeia) handleRecords(w http.ResponseWriter, r *http.Request) { 237 log.Tracef("handleRecords") 238 239 // Decode request 240 var rgb v2.Records 241 decoder := json.NewDecoder(r.Body) 242 if err := decoder.Decode(&rgb); err != nil { 243 respondWithErrorV2(w, r, "handleRecords: unmarshal", 244 v2.UserErrorReply{ 245 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 246 }) 247 return 248 } 249 challenge, err := hex.DecodeString(rgb.Challenge) 250 if err != nil || len(challenge) != v2.ChallengeSize { 251 respondWithErrorV2(w, r, "handleRecords: decode challenge", 252 v2.UserErrorReply{ 253 ErrorCode: v2.ErrorCodeChallengeInvalid, 254 }) 255 return 256 } 257 258 // Verify page size 259 if len(rgb.Requests) > int(v2.RecordsPageSize) { 260 respondWithErrorV2(w, r, "handleRecords: unmarshal", 261 v2.UserErrorReply{ 262 ErrorCode: v2.ErrorCodePageSizeExceeded, 263 }) 264 return 265 } 266 267 // Get record batch 268 reqs := convertRecordRequestsToBackend(rgb.Requests) 269 brecords, err := p.backendv2.Records(reqs) 270 if err != nil { 271 respondWithErrorV2(w, r, 272 "handleRecordGet: Records: %v", err) 273 return 274 } 275 276 // Prepare reply 277 records := make(map[string]v2.Record, len(brecords)) 278 for k, v := range brecords { 279 records[k] = p.convertRecordToV2(v) 280 } 281 response := p.identity.SignMessage(challenge) 282 reply := v2.RecordsReply{ 283 Response: hex.EncodeToString(response[:]), 284 Records: records, 285 } 286 287 util.RespondWithJSON(w, http.StatusOK, reply) 288 } 289 290 func (p *politeia) handleRecordTimestamps(w http.ResponseWriter, r *http.Request) { 291 log.Tracef("handleRecordTimestamps") 292 293 // Decode request 294 var rgt v2.RecordTimestamps 295 decoder := json.NewDecoder(r.Body) 296 if err := decoder.Decode(&rgt); err != nil { 297 respondWithErrorV2(w, r, "handleRecordTimestamps: unmarshal", 298 v2.UserErrorReply{ 299 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 300 }) 301 return 302 } 303 challenge, err := hex.DecodeString(rgt.Challenge) 304 if err != nil || len(challenge) != v2.ChallengeSize { 305 respondWithErrorV2(w, r, "handleRecordTimestamps: decode challenge", 306 v2.UserErrorReply{ 307 ErrorCode: v2.ErrorCodeChallengeInvalid, 308 }) 309 return 310 } 311 token, err := decodeTokenAnyLength(rgt.Token) 312 if err != nil { 313 respondWithErrorV2(w, r, "handleRecordTimestamps: decode token", 314 v2.UserErrorReply{ 315 ErrorCode: v2.ErrorCodeTokenInvalid, 316 ErrorContext: util.TokenRegexp(), 317 }) 318 return 319 } 320 321 // Get record timestamps 322 rt, err := p.backendv2.RecordTimestamps(token, rgt.Version) 323 if err != nil { 324 respondWithErrorV2(w, r, 325 "handleRecordTimestamps: RecordTimestamps: %v", err) 326 return 327 } 328 329 // Prepare reply 330 response := p.identity.SignMessage(challenge) 331 rtr := v2.RecordTimestampsReply{ 332 Response: hex.EncodeToString(response[:]), 333 RecordMetadata: convertTimestampToV2(rt.RecordMetadata), 334 Metadata: convertMetadataTimestampsToV2(rt.Metadata), 335 Files: convertFileTimestampsToV2(rt.Files), 336 } 337 338 util.RespondWithJSON(w, http.StatusOK, rtr) 339 } 340 341 func (p *politeia) handleInventory(w http.ResponseWriter, r *http.Request) { 342 log.Tracef("handleInventory") 343 344 // Decode request 345 var i v2.Inventory 346 decoder := json.NewDecoder(r.Body) 347 if err := decoder.Decode(&i); err != nil { 348 respondWithErrorV2(w, r, "handleInventory: unmarshal", 349 v2.UserErrorReply{ 350 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 351 }) 352 return 353 } 354 challenge, err := hex.DecodeString(i.Challenge) 355 if err != nil || len(challenge) != v2.ChallengeSize { 356 respondWithErrorV2(w, r, "handleInventory: decode challenge", 357 v2.UserErrorReply{ 358 ErrorCode: v2.ErrorCodeChallengeInvalid, 359 }) 360 return 361 } 362 363 // Verify inventory arguments. These arguments are optional. Only 364 // return an error if the arguments have been provided. 365 var ( 366 state backendv2.StateT 367 status backendv2.StatusT 368 pageSize = v2.InventoryPageSize 369 pageNumber = i.Page 370 ) 371 if i.State != v2.RecordStateInvalid { 372 state = convertRecordStateToBackend(i.State) 373 if state == backendv2.StateInvalid { 374 respondWithErrorV2(w, r, "", 375 v2.UserErrorReply{ 376 ErrorCode: v2.ErrorCodeRecordStateInvalid, 377 }) 378 return 379 } 380 } 381 if i.Status != v2.RecordStatusInvalid { 382 status = convertRecordStatusToBackend(i.Status) 383 if status == backendv2.StatusInvalid { 384 respondWithErrorV2(w, r, "", 385 v2.UserErrorReply{ 386 ErrorCode: v2.ErrorCodeRecordStatusInvalid, 387 }) 388 return 389 } 390 } 391 392 // Get inventory 393 inv, err := p.backendv2.Inventory(state, status, pageSize, pageNumber) 394 if err != nil { 395 respondWithErrorV2(w, r, 396 "handleInventory: Inventory: %v", err) 397 return 398 } 399 400 // Prepare reply 401 unvetted := make(map[string][]string, len(inv.Unvetted)) 402 for k, v := range inv.Unvetted { 403 key := backendv2.Statuses[k] 404 unvetted[key] = v 405 } 406 vetted := make(map[string][]string, len(inv.Vetted)) 407 for k, v := range inv.Vetted { 408 key := backendv2.Statuses[k] 409 vetted[key] = v 410 } 411 response := p.identity.SignMessage(challenge) 412 ir := v2.InventoryReply{ 413 Response: hex.EncodeToString(response[:]), 414 Unvetted: unvetted, 415 Vetted: vetted, 416 } 417 418 util.RespondWithJSON(w, http.StatusOK, ir) 419 } 420 421 func (p *politeia) handleInventoryOrdered(w http.ResponseWriter, r *http.Request) { 422 log.Tracef("handleInventoryOrdered") 423 424 // Decode request 425 var i v2.InventoryOrdered 426 decoder := json.NewDecoder(r.Body) 427 if err := decoder.Decode(&i); err != nil { 428 respondWithErrorV2(w, r, "handleInventoryOrdered: unmarshal", 429 v2.UserErrorReply{ 430 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 431 }) 432 return 433 } 434 challenge, err := hex.DecodeString(i.Challenge) 435 if err != nil || len(challenge) != v2.ChallengeSize { 436 respondWithErrorV2(w, r, "handleInventoryOrdered: decode challenge", 437 v2.UserErrorReply{ 438 ErrorCode: v2.ErrorCodeChallengeInvalid, 439 }) 440 return 441 } 442 443 // Verify record state 444 var state backendv2.StateT 445 if i.State != v2.RecordStateInvalid { 446 state = convertRecordStateToBackend(i.State) 447 if state == backendv2.StateInvalid { 448 respondWithErrorV2(w, r, "", 449 v2.UserErrorReply{ 450 ErrorCode: v2.ErrorCodeRecordStateInvalid, 451 }) 452 return 453 } 454 } 455 456 // Get inventory 457 tokens, err := p.backendv2.InventoryOrdered(state, 458 v2.InventoryPageSize, i.Page) 459 if err != nil { 460 respondWithErrorV2(w, r, 461 "handleInventoryOrdered: InventoryOrdered: %v", err) 462 return 463 } 464 465 response := p.identity.SignMessage(challenge) 466 ir := v2.InventoryOrderedReply{ 467 Response: hex.EncodeToString(response[:]), 468 Tokens: tokens, 469 } 470 471 util.RespondWithJSON(w, http.StatusOK, ir) 472 } 473 474 func (p *politeia) handlePluginWrite(w http.ResponseWriter, r *http.Request) { 475 log.Tracef("handlePluginWrite") 476 477 // Decode request 478 var pw v2.PluginWrite 479 decoder := json.NewDecoder(r.Body) 480 if err := decoder.Decode(&pw); err != nil { 481 respondWithErrorV2(w, r, "handlePluginWrite: unmarshal", 482 v2.UserErrorReply{ 483 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 484 }) 485 return 486 } 487 challenge, err := hex.DecodeString(pw.Challenge) 488 if err != nil || len(challenge) != v2.ChallengeSize { 489 respondWithErrorV2(w, r, "handlePluginWrite: decode challenge", 490 v2.UserErrorReply{ 491 ErrorCode: v2.ErrorCodeChallengeInvalid, 492 }) 493 return 494 } 495 token, err := decodeToken(pw.Cmd.Token) 496 if err != nil { 497 respondWithErrorV2(w, r, "handlePluginWrite: decode token", 498 v2.UserErrorReply{ 499 ErrorCode: v2.ErrorCodeTokenInvalid, 500 ErrorContext: util.TokenRegexp(), 501 }) 502 return 503 } 504 505 // Execute plugin cmd 506 payload, err := p.backendv2.PluginWrite(token, pw.Cmd.ID, 507 pw.Cmd.Command, pw.Cmd.Payload) 508 if err != nil { 509 respondWithErrorV2(w, r, 510 "handlePluginWrite: PluginWrite: %v", err) 511 return 512 } 513 514 // Prepare reply 515 response := p.identity.SignMessage(challenge) 516 pwr := v2.PluginWriteReply{ 517 Response: hex.EncodeToString(response[:]), 518 Payload: payload, 519 } 520 521 log.Infof("%v Plugin '%v' write cmd '%v' executed", 522 util.RemoteAddr(r), pw.Cmd.ID, pw.Cmd.Command) 523 524 util.RespondWithJSON(w, http.StatusOK, pwr) 525 } 526 527 func (p *politeia) handlePluginReads(w http.ResponseWriter, r *http.Request) { 528 log.Tracef("handlePluginReads") 529 530 // Decode request 531 var pr v2.PluginReads 532 decoder := json.NewDecoder(r.Body) 533 if err := decoder.Decode(&pr); err != nil { 534 respondWithErrorV2(w, r, "handlePluginReads: unmarshal", 535 v2.UserErrorReply{ 536 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 537 }) 538 return 539 } 540 challenge, err := hex.DecodeString(pr.Challenge) 541 if err != nil || len(challenge) != v2.ChallengeSize { 542 respondWithErrorV2(w, r, "handlePluginReads: decode challenge", 543 v2.UserErrorReply{ 544 ErrorCode: v2.ErrorCodeChallengeInvalid, 545 }) 546 return 547 } 548 549 // Execute the batch of read cmds 550 batch := newBatch(pr.Cmds) 551 batch.execConcurrently(p.backendv2.PluginRead) 552 553 // Prepare the replies 554 replies := make([]v2.PluginCmdReply, len(pr.Cmds)) 555 for k, v := range batch.entries { 556 if v.err == nil { 557 // Command executed successfully 558 replies[k] = v2.PluginCmdReply{ 559 Token: v.cmd.Token, 560 ID: v.cmd.ID, 561 Command: v.cmd.Command, 562 Payload: v.reply, 563 } 564 continue 565 } 566 567 // An error was encountered. Plugin and user errors 568 // are returned in valid replies. Unexpected errors 569 // cause a 500 and the whole batch to be aborted. 570 var ( 571 pluginErr backendv2.PluginError 572 userErr v2.UserErrorReply 573 userErrCode = convertErrorToV2(v.err) 574 ) 575 switch { 576 case errors.As(v.err, &pluginErr): 577 // A plugin error was returned 578 replies[k] = v2.PluginCmdReply{ 579 PluginError: &v2.PluginErrorReply{ 580 PluginID: pluginErr.PluginID, 581 ErrorCode: pluginErr.ErrorCode, 582 ErrorContext: pluginErr.ErrorContext, 583 }, 584 } 585 586 case errors.As(v.err, &userErr): 587 // A user error was returned 588 replies[k] = v2.PluginCmdReply{ 589 UserError: &userErr, 590 } 591 592 case userErrCode != v2.ErrorCodeInvalid: 593 // Backend error was returned that was 594 // converted into a valid user error. 595 replies[k] = v2.PluginCmdReply{ 596 UserError: &v2.UserErrorReply{ 597 ErrorCode: userErrCode, 598 }, 599 } 600 601 default: 602 // Internal server error. Log it and return a 500. 603 t := time.Now().Unix() 604 e := fmt.Sprintf("PluginRead %v %v %v: %v", 605 v.cmd.ID, v.cmd.Command, v.cmd.Payload, v.err) 606 log.Errorf("%v %v %v %v Internal error %v: %v", 607 util.RemoteAddr(r), r.Method, r.URL, r.Proto, t, e) 608 log.Errorf("Stacktrace (NOT A REAL CRASH): %s", debug.Stack()) 609 610 util.RespondWithJSON(w, http.StatusInternalServerError, 611 v2.ServerErrorReply{ 612 ErrorCode: t, 613 }) 614 return 615 } 616 } 617 618 // Prepare reply 619 response := p.identity.SignMessage(challenge) 620 prr := v2.PluginReadsReply{ 621 Response: hex.EncodeToString(response[:]), 622 Replies: replies, 623 } 624 625 util.RespondWithJSON(w, http.StatusOK, prr) 626 627 } 628 629 func (p *politeia) handlePluginInventory(w http.ResponseWriter, r *http.Request) { 630 log.Tracef("handlePluginInventory") 631 632 // Decode request 633 var pi v2.PluginInventory 634 decoder := json.NewDecoder(r.Body) 635 if err := decoder.Decode(&pi); err != nil { 636 respondWithErrorV2(w, r, "handlePluginInventory: unmarshal", 637 v2.UserErrorReply{ 638 ErrorCode: v2.ErrorCodeRequestPayloadInvalid, 639 }) 640 return 641 } 642 challenge, err := hex.DecodeString(pi.Challenge) 643 if err != nil || len(challenge) != v2.ChallengeSize { 644 respondWithErrorV2(w, r, "handlePluginInventory: decode challenge", 645 v2.UserErrorReply{ 646 ErrorCode: v2.ErrorCodeChallengeInvalid, 647 }) 648 return 649 } 650 651 // Get plugin inventory 652 plugins := p.backendv2.PluginInventory() 653 654 // Prepare reply 655 response := p.identity.SignMessage(challenge) 656 ir := v2.PluginInventoryReply{ 657 Response: hex.EncodeToString(response[:]), 658 Plugins: convertPluginsToV2(plugins), 659 } 660 661 util.RespondWithJSON(w, http.StatusOK, ir) 662 663 } 664 665 // decodeToken decodes a v2 token and errors if the token is not the full 666 // length token. 667 func decodeToken(token string) ([]byte, error) { 668 return util.TokenDecode(util.TokenTypeTstore, token) 669 } 670 671 // decodeTokenAnyLength decodes a v2 token. It accepts both the full length 672 // token and the short token. 673 func decodeTokenAnyLength(token string) ([]byte, error) { 674 return util.TokenDecodeAnyLength(util.TokenTypeTstore, token) 675 } 676 677 func (p *politeia) convertRecordToV2(r backendv2.Record) v2.Record { 678 var ( 679 metadata = convertMetadataStreamsToV2(r.Metadata) 680 files = convertFilesToV2(r.Files) 681 rm = r.RecordMetadata 682 sig = p.identity.SignMessage([]byte(rm.Merkle + rm.Token)) 683 ) 684 return v2.Record{ 685 State: v2.RecordStateT(rm.State), 686 Status: v2.RecordStatusT(rm.Status), 687 Version: rm.Version, 688 Timestamp: rm.Timestamp, 689 Metadata: metadata, 690 Files: files, 691 CensorshipRecord: v2.CensorshipRecord{ 692 Token: rm.Token, 693 Merkle: rm.Merkle, 694 Signature: hex.EncodeToString(sig[:]), 695 }, 696 } 697 } 698 699 func convertMetadataStreamsToBackend(metadata []v2.MetadataStream) []backendv2.MetadataStream { 700 ms := make([]backendv2.MetadataStream, 0, len(metadata)) 701 for _, v := range metadata { 702 ms = append(ms, backendv2.MetadataStream{ 703 PluginID: v.PluginID, 704 StreamID: v.StreamID, 705 Payload: v.Payload, 706 }) 707 } 708 return ms 709 } 710 711 func convertMetadataStreamsToV2(metadata []backendv2.MetadataStream) []v2.MetadataStream { 712 ms := make([]v2.MetadataStream, 0, len(metadata)) 713 for _, v := range metadata { 714 ms = append(ms, v2.MetadataStream{ 715 PluginID: v.PluginID, 716 StreamID: v.StreamID, 717 Payload: v.Payload, 718 }) 719 } 720 return ms 721 } 722 723 func convertFilesToBackend(files []v2.File) []backendv2.File { 724 fs := make([]backendv2.File, 0, len(files)) 725 for _, v := range files { 726 fs = append(fs, backendv2.File{ 727 Name: v.Name, 728 MIME: v.MIME, 729 Digest: v.Digest, 730 Payload: v.Payload, 731 }) 732 } 733 return fs 734 } 735 736 func convertFilesToV2(files []backendv2.File) []v2.File { 737 fs := make([]v2.File, 0, len(files)) 738 for _, v := range files { 739 fs = append(fs, v2.File{ 740 Name: v.Name, 741 MIME: v.MIME, 742 Digest: v.Digest, 743 Payload: v.Payload, 744 }) 745 } 746 return fs 747 } 748 749 func convertRecordRequestsToBackend(reqs []v2.RecordRequest) []backendv2.RecordRequest { 750 r := make([]backendv2.RecordRequest, 0, len(reqs)) 751 for _, v := range reqs { 752 token, err := decodeTokenAnyLength(v.Token) 753 if err != nil { 754 // Records with errors will not be included in the reply 755 log.Debugf("convertRecordRequestsToBackend: decode token: %v", err) 756 continue 757 } 758 r = append(r, backendv2.RecordRequest{ 759 Token: token, 760 Version: v.Version, 761 Filenames: v.Filenames, 762 OmitAllFiles: v.OmitAllFiles, 763 }) 764 } 765 return r 766 } 767 768 func convertProofToV2(p backendv2.Proof) v2.Proof { 769 return v2.Proof{ 770 Type: p.Type, 771 Digest: p.Digest, 772 MerkleRoot: p.MerkleRoot, 773 MerklePath: p.MerklePath, 774 ExtraData: p.ExtraData, 775 } 776 } 777 778 func convertTimestampToV2(t backendv2.Timestamp) v2.Timestamp { 779 proofs := make([]v2.Proof, 0, len(t.Proofs)) 780 for _, v := range t.Proofs { 781 proofs = append(proofs, convertProofToV2(v)) 782 } 783 return v2.Timestamp{ 784 Data: t.Data, 785 Digest: t.Digest, 786 TxID: t.TxID, 787 MerkleRoot: t.MerkleRoot, 788 Proofs: proofs, 789 } 790 } 791 792 func convertMetadataTimestampsToV2(metadata map[string]map[uint32]backendv2.Timestamp) map[string]map[uint32]v2.Timestamp { 793 md := make(map[string]map[uint32]v2.Timestamp, 16) 794 for pluginID, v := range metadata { 795 timestamps, ok := md[pluginID] 796 if !ok { 797 timestamps = make(map[uint32]v2.Timestamp, 16) 798 } 799 for streamID, ts := range v { 800 timestamps[streamID] = convertTimestampToV2(ts) 801 } 802 md[pluginID] = timestamps 803 } 804 return md 805 } 806 807 func convertFileTimestampsToV2(files map[string]backendv2.Timestamp) map[string]v2.Timestamp { 808 fs := make(map[string]v2.Timestamp, len(files)) 809 for k, v := range files { 810 fs[k] = convertTimestampToV2(v) 811 } 812 return fs 813 } 814 815 func convertRecordStateToBackend(s v2.RecordStateT) backendv2.StateT { 816 switch s { 817 case v2.RecordStateUnvetted: 818 return backendv2.StateUnvetted 819 case v2.RecordStateVetted: 820 return backendv2.StateVetted 821 } 822 return backendv2.StateInvalid 823 } 824 825 func convertRecordStatusToBackend(s v2.RecordStatusT) backendv2.StatusT { 826 switch s { 827 case v2.RecordStatusUnreviewed: 828 return backendv2.StatusUnreviewed 829 case v2.RecordStatusPublic: 830 return backendv2.StatusPublic 831 case v2.RecordStatusCensored: 832 return backendv2.StatusCensored 833 case v2.RecordStatusArchived: 834 return backendv2.StatusArchived 835 } 836 return backendv2.StatusInvalid 837 } 838 839 func convertPluginSettingToV2(p backendv2.PluginSetting) v2.PluginSetting { 840 return v2.PluginSetting{ 841 Key: p.Key, 842 Value: p.Value, 843 } 844 } 845 846 func convertPluginsToV2(bplugins []backendv2.Plugin) []v2.Plugin { 847 plugins := make([]v2.Plugin, 0, len(bplugins)) 848 for _, v := range bplugins { 849 settings := make([]v2.PluginSetting, 0, len(v.Settings)) 850 for _, v := range v.Settings { 851 settings = append(settings, convertPluginSettingToV2(v)) 852 } 853 plugins = append(plugins, v2.Plugin{ 854 ID: v.ID, 855 Settings: settings, 856 }) 857 } 858 return plugins 859 } 860 861 func respondWithErrorV2(w http.ResponseWriter, r *http.Request, format string, err error) { 862 var ( 863 errCode = convertErrorToV2(err) 864 ue v2.UserErrorReply 865 ce backendv2.ContentError 866 ste backendv2.StatusTransitionError 867 pe backendv2.PluginError 868 ) 869 switch { 870 case errCode != v2.ErrorCodeInvalid: 871 // Backend error 872 log.Infof("%v User error: %v %v", util.RemoteAddr(r), 873 errCode, v2.ErrorCodes[errCode]) 874 util.RespondWithJSON(w, http.StatusBadRequest, 875 v2.UserErrorReply{ 876 ErrorCode: errCode, 877 }) 878 return 879 880 case errors.As(err, &ue): 881 // Politeiad user error 882 m := fmt.Sprintf("%v User error: %v %v", util.RemoteAddr(r), 883 ue.ErrorCode, v2.ErrorCodes[ue.ErrorCode]) 884 if ce.ErrorContext != "" { 885 m += fmt.Sprintf(": %v", ce.ErrorContext) 886 } 887 log.Infof(m) 888 util.RespondWithJSON(w, http.StatusBadRequest, ue) 889 return 890 891 case errors.As(err, &ce): 892 // Backend content error 893 errCode := convertContentErrorToV2(ce.ErrorCode) 894 m := fmt.Sprintf("%v User error: %v %v", util.RemoteAddr(r), 895 errCode, v2.ErrorCodes[errCode]) 896 if ce.ErrorContext != "" { 897 m += fmt.Sprintf(": %v", ce.ErrorContext) 898 } 899 log.Infof(m) 900 util.RespondWithJSON(w, http.StatusBadRequest, 901 v2.UserErrorReply{ 902 ErrorCode: errCode, 903 ErrorContext: ce.ErrorContext, 904 }) 905 return 906 907 case errors.As(err, &ste): 908 // Backend status transition error 909 log.Infof("%v User error: %v", util.RemoteAddr(r), ste.Error()) 910 util.RespondWithJSON(w, http.StatusBadRequest, 911 v2.UserErrorReply{ 912 ErrorCode: v2.ErrorCodeStatusChangeInvalid, 913 ErrorContext: ste.Error(), 914 }) 915 return 916 917 case errors.As(err, &pe): 918 // Plugin user error 919 m := fmt.Sprintf("%v Plugin error: %v %v", 920 util.RemoteAddr(r), pe.PluginID, pe.ErrorCode) 921 if pe.ErrorContext != "" { 922 m += fmt.Sprintf(": %v", pe.ErrorContext) 923 } 924 log.Infof(m) 925 util.RespondWithJSON(w, http.StatusBadRequest, 926 v2.PluginErrorReply{ 927 PluginID: pe.PluginID, 928 ErrorCode: pe.ErrorCode, 929 ErrorContext: pe.ErrorContext, 930 }) 931 return 932 } 933 934 // Internal server error. Log it and return a 500. 935 t := time.Now().Unix() 936 e := fmt.Sprintf(format, err) 937 log.Errorf("%v %v %v %v Internal error %v: %v", 938 util.RemoteAddr(r), r.Method, r.URL, r.Proto, t, e) 939 log.Errorf("Stacktrace (NOT A REAL CRASH): %s", debug.Stack()) 940 941 util.RespondWithJSON(w, http.StatusInternalServerError, 942 v2.ServerErrorReply{ 943 ErrorCode: t, 944 }) 945 } 946 947 func convertErrorToV2(e error) v2.ErrorCodeT { 948 switch e { 949 case backendv2.ErrTokenInvalid: 950 return v2.ErrorCodeTokenInvalid 951 case backendv2.ErrRecordNotFound: 952 return v2.ErrorCodeRecordNotFound 953 case backendv2.ErrRecordLocked: 954 return v2.ErrorCodeRecordLocked 955 case backendv2.ErrNoRecordChanges: 956 return v2.ErrorCodeNoRecordChanges 957 case backendv2.ErrPluginIDInvalid: 958 return v2.ErrorCodePluginIDInvalid 959 case backendv2.ErrPluginCmdInvalid: 960 return v2.ErrorCodePluginCmdInvalid 961 case backendv2.ErrDuplicatePayload: 962 return v2.ErrorCodeDuplicatePayload 963 } 964 return v2.ErrorCodeInvalid 965 } 966 967 func convertContentErrorToV2(e backendv2.ContentErrorCodeT) v2.ErrorCodeT { 968 switch e { 969 case backendv2.ContentErrorMetadataStreamInvalid: 970 return v2.ErrorCodeMetadataStreamInvalid 971 case backendv2.ContentErrorMetadataStreamDuplicate: 972 return v2.ErrorCodeMetadataStreamDuplicate 973 case backendv2.ContentErrorFilesEmpty: 974 return v2.ErrorCodeFilesEmpty 975 case backendv2.ContentErrorFileNameInvalid: 976 return v2.ErrorCodeFileNameInvalid 977 case backendv2.ContentErrorFileNameDuplicate: 978 return v2.ErrorCodeFileNameDuplicate 979 case backendv2.ContentErrorFileDigestInvalid: 980 return v2.ErrorCodeFileDigestInvalid 981 case backendv2.ContentErrorFilePayloadInvalid: 982 return v2.ErrorCodeFilePayloadInvalid 983 case backendv2.ContentErrorFileMIMETypeInvalid: 984 return v2.ErrorCodeFileMIMETypeInvalid 985 case backendv2.ContentErrorFileMIMETypeUnsupported: 986 return v2.ErrorCodeFileMIMETypeUnsupported 987 } 988 return v2.ErrorCodeInvalid 989 }