diff --git a/server/api.go b/server/api.go index c7ac948..633f9a0 100644 --- a/server/api.go +++ b/server/api.go @@ -1,10 +1,15 @@ package main import ( + "archive/zip" + "bufio" "encoding/json" + "fmt" "github.com/gorilla/mux" "github.com/pkg/errors" + "io" "net/http" + "time" mattermostModel "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" @@ -36,6 +41,7 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req router.HandleFunc("/api/v1/legalhold/create", p.createLegalHold) router.HandleFunc("/api/v1/legalhold/{legalhold_id:[A-Za-z0-9]+}/release", p.releaseLegalHold) router.HandleFunc("/api/v1/legalhold/{legalhold_id:[A-Za-z0-9]+}/update", p.updateLegalHold) + router.HandleFunc("/api/v1/legalhold/{legalhold_id:[A-Za-z0-9]+}/download", p.downloadLegalHold) p.router = router p.router.ServeHTTP(w, r) @@ -204,6 +210,81 @@ func (p *Plugin) updateLegalHold(w http.ResponseWriter, r *http.Request) { } } +func (p *Plugin) downloadLegalHold(w http.ResponseWriter, r *http.Request) { + // Get the LegalHold. + legalholdID, err := RequireLegalHoldID(r) + if err != nil { + http.Error(w, "failed to parse LegalHold ID", http.StatusBadRequest) + p.Client.Log.Error(err.Error()) + return + } + + legalHold, err := p.KVStore.GetLegalHoldByID(legalholdID) + if err != nil { + http.Error(w, "failed to download legal hold", http.StatusInternalServerError) + p.Client.Log.Error(err.Error()) + return + } + + // Get the list of files to include in the download. + files, err := p.FileBackend.ListDirectoryRecursively(legalHold.BasePath()) + if err != nil { + http.Error(w, "failed to download legal hold", http.StatusInternalServerError) + p.Client.Log.Error(err.Error()) + return + } + + // Write headers for the zip file. + w.Header().Set("Content-Type", "application/zip") + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", "legalholddata.zip")) + w.WriteHeader(http.StatusOK) + + // Write the files to the download on-the-fly. + zipWriter := zip.NewWriter(w) + for _, entry := range files { + header := &zip.FileHeader{ + Name: entry, + Method: zip.Deflate, // deflate also works, but at a cost + Modified: time.Now(), + } + + entryWriter, err := zipWriter.CreateHeader(header) + if err != nil { + http.Error(w, "failed to download legal hold", http.StatusInternalServerError) + p.Client.Log.Error(err.Error()) + return + } + + backendReader, err := p.FileBackend.Reader(entry) + if err != nil { + http.Error(w, "failed to download legal hold", http.StatusInternalServerError) + p.Client.Log.Error(err.Error()) + return + } + + fileReader := bufio.NewReader(backendReader) + + _, err = io.Copy(entryWriter, fileReader) + if err != nil { + http.Error(w, "failed to download legal hold", http.StatusInternalServerError) + p.Client.Log.Error(err.Error()) + return + } + + if err = zipWriter.Flush(); err != nil { + http.Error(w, "failed to download legal hold", http.StatusInternalServerError) + p.Client.Log.Error(err.Error()) + return + } + } + + if err := zipWriter.Close(); err != nil { + http.Error(w, "failed to download legal hold", http.StatusInternalServerError) + p.Client.Log.Error(err.Error()) + return + } +} + func RequireLegalHoldID(r *http.Request) (string, error) { props := mux.Vars(r) diff --git a/webapp/src/client.ts b/webapp/src/client.ts index 87d4773..83db399 100644 --- a/webapp/src/client.ts +++ b/webapp/src/client.ts @@ -8,6 +8,10 @@ class APIClient { private readonly url = `/plugins/${manifest.id}/api/v1`; private readonly client4 = new Client4(); + downloadUrl = (id: string) => { + return `${this.url}/legalhold/${id}/download`; + } + getLegalHolds = () => { const url = `${this.url}/legalhold/list`; return this.doGet(url); diff --git a/webapp/src/components/legal_hold_table/legal_hold_row/legal_hold_row.tsx b/webapp/src/components/legal_hold_table/legal_hold_row/legal_hold_row.tsx index 57c544d..e3c8e78 100644 --- a/webapp/src/components/legal_hold_table/legal_hold_row/legal_hold_row.tsx +++ b/webapp/src/components/legal_hold_table/legal_hold_row/legal_hold_row.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {UserProfile} from 'mattermost-redux/types/users'; import {LegalHold} from '@/types'; +import Client from "@/client"; interface LegalHoldRowProps { legalHold: LegalHold; @@ -26,6 +27,8 @@ const LegalHoldRow = (props: LegalHoldRowProps) => { return 'loading...'; }); + const downloadUrl = Client.downloadUrl(lh.id); + return (
{lh.display_name}
@@ -40,7 +43,7 @@ const LegalHoldRow = (props: LegalHoldRowProps) => { {'Edit'} {' '} - {'Download'} + {'Download'} {' '}