package service import ( "encoding/json" "fmt" "io" "net/http" "github.com/fleetdm/fleet/v4/server/fleet" ) // ListCarves lists the file carving sessions func (c *Client) ListCarves(opt fleet.CarveListOptions) ([]*fleet.CarveMetadata, error) { endpoint := "/api/latest/fleet/carves" rawQuery := "" if opt.Expired { rawQuery = "expired=1" } response, err := c.AuthenticatedDo("GET", endpoint, rawQuery, nil) if err != nil { return nil, fmt.Errorf("GET /api/latest/fleet/carves: %w", err) } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf( "list carves received status %d %s", response.StatusCode, extractServerErrorText(response.Body), ) } var responseBody listCarvesResponse err = json.NewDecoder(response.Body).Decode(&responseBody) if err != nil { return nil, fmt.Errorf("decode get carves response: %w", err) } if responseBody.Err != nil { return nil, fmt.Errorf("get carves: %s", responseBody.Err) } carves := []*fleet.CarveMetadata{} for _, carve := range responseBody.Carves { c := carve carves = append(carves, &c) } return carves, nil } func (c *Client) GetCarve(carveId int64) (*fleet.CarveMetadata, error) { endpoint := fmt.Sprintf("/api/latest/fleet/carves/%d", carveId) response, err := c.AuthenticatedDo("GET", endpoint, "", nil) if err != nil { return nil, fmt.Errorf("GET "+endpoint+": %w", err) } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf( "get carve received status %d %s", response.StatusCode, extractServerErrorText(response.Body), ) } var responseBody getCarveResponse err = json.NewDecoder(response.Body).Decode(&responseBody) if err != nil { return nil, fmt.Errorf("decode carve response: %w", err) } if responseBody.Err != nil { return nil, fmt.Errorf("get carve: %s", responseBody.Err) } return &responseBody.Carve, nil } func (c *Client) getCarveBlock(carveId, blockId int64) ([]byte, error) { path := fmt.Sprintf( "/api/latest/fleet/carves/%d/block/%d", carveId, blockId, ) response, err := c.AuthenticatedDo("GET", path, "", nil) if err != nil { return nil, fmt.Errorf("GET %s: %w", path, err) } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf( "get carve block received status %d: %s", response.StatusCode, extractServerErrorText(response.Body), ) } var responseBody getCarveBlockResponse err = json.NewDecoder(response.Body).Decode(&responseBody) if err != nil { return nil, fmt.Errorf("decode get carve block response: %w", err) } if responseBody.Err != nil { return nil, fmt.Errorf("get carve block: %s", responseBody.Err) } return responseBody.Data, nil } type carveReader struct { carve fleet.CarveMetadata bytesRead int64 curBlock int64 buffer []byte client *Client } func newCarveReader(carve fleet.CarveMetadata, client *Client) *carveReader { return &carveReader{ carve: carve, client: client, bytesRead: 0, curBlock: 0, } } func (r *carveReader) Read(p []byte) (n int, err error) { if len(p) == 0 { return 0, nil } if r.bytesRead >= r.carve.CarveSize { return 0, io.EOF } // Load data from API if necessary if len(r.buffer) == 0 { var err error r.buffer, err = r.client.getCarveBlock(r.carve.ID, r.curBlock) if err != nil { return 0, fmt.Errorf("get block %d: %w", r.curBlock, err) } r.curBlock++ } // Calculate length we can copy copyLen := len(p) if copyLen > len(r.buffer) { copyLen = len(r.buffer) } // Perform copy and clear copied contents from buffer copy(p, r.buffer[:copyLen]) r.buffer = r.buffer[copyLen:] r.bytesRead += int64(copyLen) return copyLen, nil } // DownloadCarve creates a Reader downloading a carve (by ID) func (c *Client) DownloadCarve(id int64) (io.Reader, error) { path := fmt.Sprintf("/api/latest/fleet/carves/%d", id) response, err := c.AuthenticatedDo("GET", path, "", nil) if err != nil { return nil, fmt.Errorf("GET %s: %w", path, err) } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf( "download carve received status %d: %s", response.StatusCode, extractServerErrorText(response.Body), ) } var responseBody getCarveResponse err = json.NewDecoder(response.Body).Decode(&responseBody) if err != nil { return nil, fmt.Errorf("decode get carve by name response: %w", err) } if responseBody.Err != nil { return nil, fmt.Errorf("get carve by name: %s", responseBody.Err) } reader := newCarveReader(responseBody.Carve, c) return reader, nil }