mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Compare commits
12 Commits
dependabot
...
add-interc
Author | SHA1 | Date | |
---|---|---|---|
a9f6701145 | |||
1a45ea36a4 | |||
38bd04b80b | |||
71acd6e9ef | |||
fe97d06bb5 | |||
89141afd3b | |||
cf55456c42 | |||
f4074a8060 | |||
d051d48941 | |||
e1067ecffb | |||
9dd8464af7 | |||
71e550f0cd |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@ -1,3 +1,3 @@
|
||||
github: dstotijn
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: dstotijn
|
||||
custom: "https://www.paypal.com/paypalme/dstotijn"
|
||||
|
@ -35,7 +35,7 @@ linters-settings:
|
||||
godot:
|
||||
capital: true
|
||||
ireturn:
|
||||
allow: "error,empty,anon,stdlib,.*(or|er)$,github.com/99designs/gqlgen/graphql.Marshaler,github.com/dstotijn/hetty/pkg/api.QueryResolver,github.com/dstotijn/hetty/pkg/filter.Expression"
|
||||
allow: "error,empty,anon,stdlib,.*(or|er)$,github.com/99designs/gqlgen/graphql.Marshaler,github.com/dstotijn/hetty/pkg/api.QueryResolver,github.com/dstotijn/hetty/pkg/search.Expression"
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
|
@ -55,8 +55,13 @@ snapcrafts:
|
||||
license: MIT
|
||||
apps:
|
||||
hetty:
|
||||
command: hetty
|
||||
plugs: ["network", "network-bind"]
|
||||
plugs: ["home", "network", "network-bind", "personal-files"]
|
||||
plugs:
|
||||
personal-files:
|
||||
read:
|
||||
- $HOME/.hetty
|
||||
write:
|
||||
- $HOME/.hetty
|
||||
|
||||
scoop:
|
||||
bucket:
|
||||
@ -69,31 +74,6 @@ scoop:
|
||||
description: An HTTP toolkit for security research.
|
||||
license: MIT
|
||||
|
||||
dockers:
|
||||
- extra_files:
|
||||
- go.mod
|
||||
- go.sum
|
||||
- pkg
|
||||
- cmd
|
||||
- admin
|
||||
image_templates:
|
||||
- "ghcr.io/dstotijn/hetty:{{ .Version }}"
|
||||
- "ghcr.io/dstotijn/hetty:{{ .Major }}"
|
||||
- "ghcr.io/dstotijn/hetty:{{ .Major }}.{{ .Minor }}"
|
||||
- "ghcr.io/dstotijn/hetty:latest"
|
||||
- "dstotijn/hetty:{{ .Version }}"
|
||||
- "dstotijn/hetty:{{ .Major }}"
|
||||
- "dstotijn/hetty:{{ .Major }}.{{ .Minor }}"
|
||||
- "dstotijn/hetty:latest"
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.source=https://github.com/dstotijn/hetty"
|
||||
- "--build-arg=HETTY_VERSION={{.Version}}"
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
|
26
README.md
26
README.md
@ -17,7 +17,6 @@ features tailored to the needs of the infosec and bug bounty community.
|
||||
|
||||
- Machine-in-the-middle (MITM) HTTP proxy, with logs and advanced search
|
||||
- HTTP client for manually creating/editing requests, and replay proxied requests
|
||||
- Intercept requests and responses for manual review (edit, send/receive, cancel)
|
||||
- Scope support, to help keep work organized
|
||||
- Easy-to-use web based admin interface
|
||||
- Project based database storage, to help keep work organized
|
||||
@ -51,6 +50,11 @@ brew install hettysoft/tap/hetty
|
||||
sudo snap install hetty
|
||||
```
|
||||
|
||||
⚠️ As of Sun 6 Mar 2022, we're awaiting Canonical to approve the necessary
|
||||
Snapcraft privileges to allow Hetty to bind on a port. In the meantime, please
|
||||
use one of the Linux [releases](https://github.com/dstotijn/hetty/releases) on
|
||||
Github.
|
||||
|
||||
#### Windows
|
||||
|
||||
```sh
|
||||
@ -64,18 +68,8 @@ Alternatively, you can [download the latest release from
|
||||
GitHub](https://github.com/dstotijn/hetty/releases/latest) for your OS and
|
||||
architecture, and move the binary to a directory in your `$PATH`. If your OS is
|
||||
not available for one of the package managers or not listed in the GitHub
|
||||
releases, you can compile from source _(link coming soon)_.
|
||||
|
||||
#### Docker
|
||||
|
||||
Docker images are distributed via [GitHub's Container registry](https://github.com/dstotijn/hetty/pkgs/container/hetty)
|
||||
and [Docker Hub](https://hub.docker.com/r/dstotijn/hetty). To run Hetty via with a volume for database and certificate
|
||||
storage, and port 8080 forwarded:
|
||||
|
||||
```
|
||||
docker run -v $HOME/.hetty:/root/.hetty -p 8080:8080 \
|
||||
ghcr.io/dstotijn/hetty:latest
|
||||
```
|
||||
releases, you can compile from source _(link coming soon)_ or use a Docker image
|
||||
_(link coming soon)_.
|
||||
|
||||
### Usage
|
||||
|
||||
@ -146,11 +140,9 @@ Guidelines](CONTRIBUTING.md) for details.
|
||||
|
||||
## Sponsors
|
||||
|
||||
<p><a href="https://www.tines.com/?utm_source=oss&utm_medium=sponsorship&utm_campaign=hetty">
|
||||
<a href="https://www.tines.com/?utm_source=oss&utm_medium=sponsorship&utm_campaign=hetty">
|
||||
<img src="https://hetty.xyz/img/tines-sponsorship-badge.png" width="140" alt="Sponsored by Tines">
|
||||
</a></p>
|
||||
|
||||
💖 Are you enjoying Hetty? You can [sponsor me](https://github.com/sponsors/dstotijn)!
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
|
||||
import { KeyValuePair } from "lib/components/KeyValuePair";
|
||||
import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair";
|
||||
import Link from "lib/components/Link";
|
||||
import RequestTabs from "lib/components/RequestTabs";
|
||||
import ResponseStatus from "lib/components/ResponseStatus";
|
||||
@ -112,11 +112,11 @@ function EditRequest(): JSX.Element {
|
||||
newQueryParams.push({ key: "", value: "" });
|
||||
setQueryParams(newQueryParams);
|
||||
|
||||
const newReqHeaders = interceptedRequest.headers || [];
|
||||
const newReqHeaders = sortKeyValuePairs(interceptedRequest.headers || []);
|
||||
setReqHeaders([...newReqHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
||||
|
||||
setResBody(interceptedRequest.response?.body || "");
|
||||
const newResHeaders = interceptedRequest.response?.headers || [];
|
||||
const newResHeaders = sortKeyValuePairs(interceptedRequest.response?.headers || []);
|
||||
setResHeaders([...newResHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
||||
},
|
||||
});
|
||||
|
@ -76,7 +76,6 @@ function Search(): JSX.Element {
|
||||
<ClickAwayListener onClickAway={handleClickAway}>
|
||||
<Paper
|
||||
component="form"
|
||||
autoComplete="off"
|
||||
onSubmit={handleSubmit}
|
||||
ref={filterRef}
|
||||
sx={{
|
||||
@ -110,8 +109,6 @@ function Search(): JSX.Element {
|
||||
value={searchExpr}
|
||||
onChange={(e) => setSearchExpr(e.target.value)}
|
||||
onFocus={() => setFilterOpen(true)}
|
||||
autoCorrect="false"
|
||||
spellCheck="false"
|
||||
/>
|
||||
<Tooltip title="Search">
|
||||
<IconButton type="submit" sx={{ padding: 1.25 }}>
|
||||
|
@ -1,9 +1,8 @@
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { Alert, Box, Button, Fab, Tooltip, Typography, useTheme } from "@mui/material";
|
||||
import { Alert, Box, Button, Typography } from "@mui/material";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { KeyValuePair } from "lib/components/KeyValuePair";
|
||||
import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair";
|
||||
import RequestTabs from "lib/components/RequestTabs";
|
||||
import Response from "lib/components/Response";
|
||||
import SplitPane from "lib/components/SplitPane";
|
||||
@ -18,21 +17,15 @@ import { queryParamsFromURL } from "lib/queryParamsFromURL";
|
||||
import updateKeyPairItem from "lib/updateKeyPairItem";
|
||||
import updateURLQueryParams from "lib/updateURLQueryParams";
|
||||
|
||||
const defaultMethod = HttpMethod.Get;
|
||||
const defaultProto = HttpProto.Http20;
|
||||
const emptyKeyPair = [{ key: "", value: "" }];
|
||||
|
||||
function EditRequest(): JSX.Element {
|
||||
const router = useRouter();
|
||||
const reqId = router.query.id as string | undefined;
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const [method, setMethod] = useState(defaultMethod);
|
||||
const [method, setMethod] = useState(HttpMethod.Get);
|
||||
const [url, setURL] = useState("");
|
||||
const [proto, setProto] = useState(defaultProto);
|
||||
const [queryParams, setQueryParams] = useState<KeyValuePair[]>(emptyKeyPair);
|
||||
const [headers, setHeaders] = useState<KeyValuePair[]>(emptyKeyPair);
|
||||
const [proto, setProto] = useState(HttpProto.Http20);
|
||||
const [queryParams, setQueryParams] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
|
||||
const [headers, setHeaders] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
|
||||
const [body, setBody] = useState("");
|
||||
|
||||
const handleQueryParamChange = (key: string, value: string, idx: number) => {
|
||||
@ -90,7 +83,7 @@ function EditRequest(): JSX.Element {
|
||||
newQueryParams.push({ key: "", value: "" });
|
||||
setQueryParams(newQueryParams);
|
||||
|
||||
const newHeaders = senderRequest.headers || [];
|
||||
const newHeaders = sortKeyValuePairs(senderRequest.headers || []);
|
||||
setHeaders([...newHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
|
||||
setResponse(senderRequest.response);
|
||||
},
|
||||
@ -138,26 +131,8 @@ function EditRequest(): JSX.Element {
|
||||
createOrUpdateRequestAndSend();
|
||||
};
|
||||
|
||||
const handleNewRequest = () => {
|
||||
setURL("");
|
||||
setMethod(defaultMethod);
|
||||
setProto(defaultProto);
|
||||
setQueryParams(emptyKeyPair);
|
||||
setHeaders(emptyKeyPair);
|
||||
setBody("");
|
||||
setResponse(null);
|
||||
router.push(`/sender`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" height="100%" gap={2}>
|
||||
<Box sx={{ position: "absolute", bottom: theme.spacing(2), right: theme.spacing(2) }}>
|
||||
<Tooltip title="New request">
|
||||
<Fab color="primary" onClick={handleNewRequest}>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box component="form" autoComplete="off" onSubmit={handleFormSubmit}>
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
||||
<UrlBar
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
TextFieldProps,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import MaterialLink from "@mui/material/Link";
|
||||
import { SwitchBaseProps } from "@mui/material/internal/SwitchBase";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@ -218,13 +217,7 @@ export default function Settings(): JSX.Element {
|
||||
onChange={(e) => setInterceptReqFilter(e.target.value)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
Filter expression to match incoming requests on. When set, only matching requests are intercepted.{" "}
|
||||
<MaterialLink
|
||||
href="https://hetty.xyz/docs/guides/intercept?utm_source=hettyapp#request-filter"
|
||||
target="_blank"
|
||||
>
|
||||
Read docs.
|
||||
</MaterialLink>
|
||||
Filter expression to match incoming requests on. When set, only matching requests are intercepted.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Button
|
||||
@ -273,13 +266,7 @@ export default function Settings(): JSX.Element {
|
||||
onChange={(e) => setInterceptResFilter(e.target.value)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
Filter expression to match received responses on. When set, only matching responses are intercepted.{" "}
|
||||
<MaterialLink
|
||||
href="https://hetty.xyz/docs/guides/intercept/?utm_source=hettyapp#response-filter"
|
||||
target="_blank"
|
||||
>
|
||||
Read docs.
|
||||
</MaterialLink>
|
||||
Filter expression to match received responses on. When set, only matching responses are intercepted.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<Button
|
||||
|
@ -184,4 +184,20 @@ export function KeyValuePairTable({ items, onChange, onDelete }: KeyValuePairTab
|
||||
);
|
||||
}
|
||||
|
||||
export function sortKeyValuePairs(items: KeyValuePair[]): KeyValuePair[] {
|
||||
const sorted = [...items];
|
||||
|
||||
sorted.sort((a, b) => {
|
||||
if (a.key < b.key) {
|
||||
return -1;
|
||||
}
|
||||
if (a.key > b.key) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export default KeyValuePairTable;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
import { sortKeyValuePairs } from "./KeyValuePair";
|
||||
import ResponseTabs from "./ResponseTabs";
|
||||
|
||||
import ResponseStatus from "lib/components/ResponseStatus";
|
||||
@ -28,7 +29,7 @@ function Response({ response }: ResponseProps): JSX.Element {
|
||||
</Box>
|
||||
<ResponseTabs
|
||||
body={response?.body}
|
||||
headers={response?.headers || []}
|
||||
headers={sortKeyValuePairs(response?.headers || [])}
|
||||
hasResponse={response !== undefined && response !== null}
|
||||
/>
|
||||
</Box>
|
||||
|
1684
admin/yarn.lock
1684
admin/yarn.lock
File diff suppressed because it is too large
Load Diff
4
go.mod
4
go.mod
@ -50,8 +50,8 @@ require (
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||
golang.org/x/tools v0.1.5 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
|
22
go.sum
22
go.sum
@ -164,7 +164,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
@ -179,7 +178,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@ -189,8 +187,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -200,11 +196,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -214,7 +207,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -229,19 +221,11 @@ golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -254,8 +238,6 @@ golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -49,17 +49,3 @@ func UnmarshalURL(v interface{}) (*url.URL, error) {
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
type HTTPHeaders []HTTPHeader
|
||||
|
||||
func (h HTTPHeaders) Len() int {
|
||||
return len(h)
|
||||
}
|
||||
|
||||
func (h HTTPHeaders) Less(i, j int) bool {
|
||||
return h[i].Key < h[j].Key
|
||||
}
|
||||
|
||||
func (h HTTPHeaders) Swap(i, j int) {
|
||||
h[i], h[j] = h[j], h[i]
|
||||
}
|
||||
|
@ -11,19 +11,18 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
@ -125,8 +124,6 @@ func parseRequestLog(reqLog reqlog.RequestLog) (HTTPRequestLog, error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(HTTPHeaders(log.Headers))
|
||||
}
|
||||
|
||||
if reqLog.Response != nil {
|
||||
@ -175,8 +172,6 @@ func parseResponseLog(resLog reqlog.ResponseLog) (HTTPResponseLog, error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(HTTPHeaders(httpResLog.Headers))
|
||||
}
|
||||
|
||||
return httpResLog, nil
|
||||
@ -639,7 +634,7 @@ func (r *mutationResolver) UpdateInterceptSettings(
|
||||
}
|
||||
|
||||
if input.RequestFilter != nil && *input.RequestFilter != "" {
|
||||
expr, err := filter.ParseQuery(*input.RequestFilter)
|
||||
expr, err := search.ParseQuery(*input.RequestFilter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse request filter: %w", err)
|
||||
}
|
||||
@ -648,7 +643,7 @@ func (r *mutationResolver) UpdateInterceptSettings(
|
||||
}
|
||||
|
||||
if input.ResponseFilter != nil && *input.ResponseFilter != "" {
|
||||
expr, err := filter.ParseQuery(*input.ResponseFilter)
|
||||
expr, err := search.ParseQuery(*input.ResponseFilter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse response filter: %w", err)
|
||||
}
|
||||
@ -715,8 +710,6 @@ func parseSenderRequest(req sender.Request) (SenderRequest, error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(HTTPHeaders(senderReq.Headers))
|
||||
}
|
||||
|
||||
if len(req.Body) > 0 {
|
||||
@ -772,8 +765,6 @@ func parseHTTPRequest(req *http.Request) (HTTPRequest, error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(HTTPHeaders(httpReq.Headers))
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
@ -824,8 +815,6 @@ func parseHTTPResponse(res *http.Response) (HTTPResponse, error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(HTTPHeaders(httpRes.Headers))
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
@ -916,43 +905,43 @@ func scopeToScopeRules(rules []scope.Rule) []ScopeRule {
|
||||
return scopeRules
|
||||
}
|
||||
|
||||
func findRequestsFilterFromInput(input *HTTPRequestLogFilterInput) (findFilter reqlog.FindRequestsFilter, err error) {
|
||||
func findRequestsFilterFromInput(input *HTTPRequestLogFilterInput) (filter reqlog.FindRequestsFilter, err error) {
|
||||
if input == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if input.OnlyInScope != nil {
|
||||
findFilter.OnlyInScope = *input.OnlyInScope
|
||||
filter.OnlyInScope = *input.OnlyInScope
|
||||
}
|
||||
|
||||
if input.SearchExpression != nil && *input.SearchExpression != "" {
|
||||
expr, err := filter.ParseQuery(*input.SearchExpression)
|
||||
expr, err := search.ParseQuery(*input.SearchExpression)
|
||||
if err != nil {
|
||||
return reqlog.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
|
||||
}
|
||||
|
||||
findFilter.SearchExpr = expr
|
||||
filter.SearchExpr = expr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func findSenderRequestsFilterFromInput(input *SenderRequestFilterInput) (findFilter sender.FindRequestsFilter, err error) {
|
||||
func findSenderRequestsFilterFromInput(input *SenderRequestFilterInput) (filter sender.FindRequestsFilter, err error) {
|
||||
if input == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if input.OnlyInScope != nil {
|
||||
findFilter.OnlyInScope = *input.OnlyInScope
|
||||
filter.OnlyInScope = *input.OnlyInScope
|
||||
}
|
||||
|
||||
if input.SearchExpression != nil && *input.SearchExpression != "" {
|
||||
expr, err := filter.ParseQuery(*input.SearchExpression)
|
||||
expr, err := search.ParseQuery(*input.SearchExpression)
|
||||
if err != nil {
|
||||
return sender.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
|
||||
}
|
||||
|
||||
findFilter.SearchExpr = expr
|
||||
filter.SearchExpr = expr
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/proj"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
@ -45,7 +45,7 @@ func TestUpsertProject(t *testing.T) {
|
||||
database := DatabaseFromBadgerDB(badgerDB)
|
||||
defer database.Close()
|
||||
|
||||
searchExpr, err := filter.ParseQuery("foo AND bar OR NOT baz")
|
||||
searchExpr, err := search.ParseQuery("foo AND bar OR NOT baz")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error (expected: nil, got: %v)", err)
|
||||
}
|
||||
|
@ -1,212 +0,0 @@
|
||||
package filter_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
)
|
||||
|
||||
func TestExpressionString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expression filter.Expression
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "string literal expression",
|
||||
expression: filter.StringLiteral{Value: "foobar"},
|
||||
expected: `"foobar"`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with equal operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpEq,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
expected: `("foo" = "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with not equal operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpNotEq,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
expected: `("foo" != "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with greater than operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpGt,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
expected: `("foo" > "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with less than operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpLt,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
expected: `("foo" < "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with greater than or equal operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpGtEq,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
expected: `("foo" >= "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with less than or equal operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpLtEq,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
expected: `("foo" <= "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with regular expression operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpRe,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.RegexpLiteral{regexp.MustCompile("bar")},
|
||||
},
|
||||
expected: `("foo" =~ "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with not regular expression operator",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpNotRe,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.RegexpLiteral{regexp.MustCompile("bar")},
|
||||
},
|
||||
expected: `("foo" !~ "bar")`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with AND, OR and NOT operators",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.InfixExpression{
|
||||
Operator: filter.TokOpOr,
|
||||
Left: filter.StringLiteral{Value: "bar"},
|
||||
Right: filter.PrefixExpression{
|
||||
Operator: filter.TokOpNot,
|
||||
Right: filter.StringLiteral{Value: "baz"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: `("foo" AND ("bar" OR (NOT "baz")))`,
|
||||
},
|
||||
{
|
||||
name: "boolean expression with nested group",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpOr,
|
||||
Left: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: filter.PrefixExpression{
|
||||
Operator: filter.TokOpNot,
|
||||
Right: filter.StringLiteral{Value: "baz"},
|
||||
},
|
||||
},
|
||||
expected: `(("foo" AND "bar") OR (NOT "baz"))`,
|
||||
},
|
||||
{
|
||||
name: "implicit boolean expression with string literal operands",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: filter.StringLiteral{Value: "baz"},
|
||||
},
|
||||
expected: `(("foo" AND "bar") AND "baz")`,
|
||||
},
|
||||
{
|
||||
name: "implicit boolean expression nested in group",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
expected: `("foo" AND "bar")`,
|
||||
},
|
||||
{
|
||||
name: "implicit and explicit boolean expression with string literal operands",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.InfixExpression{
|
||||
Operator: filter.TokOpOr,
|
||||
Left: filter.StringLiteral{Value: "bar"},
|
||||
Right: filter.StringLiteral{Value: "baz"},
|
||||
},
|
||||
},
|
||||
Right: filter.StringLiteral{Value: "yolo"},
|
||||
},
|
||||
expected: `(("foo" AND ("bar" OR "baz")) AND "yolo")`,
|
||||
},
|
||||
{
|
||||
name: "implicit boolean expression with comparison operands",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpAnd,
|
||||
Left: filter.InfixExpression{
|
||||
Operator: filter.TokOpEq,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: filter.InfixExpression{
|
||||
Operator: filter.TokOpRe,
|
||||
Left: filter.StringLiteral{Value: "baz"},
|
||||
Right: filter.RegexpLiteral{regexp.MustCompile("yolo")},
|
||||
},
|
||||
},
|
||||
expected: `(("foo" = "bar") AND ("baz" =~ "yolo"))`,
|
||||
},
|
||||
{
|
||||
name: "eq operator takes precedence over boolean ops",
|
||||
expression: filter.InfixExpression{
|
||||
Operator: filter.TokOpOr,
|
||||
Left: filter.InfixExpression{
|
||||
Operator: filter.TokOpEq,
|
||||
Left: filter.StringLiteral{Value: "foo"},
|
||||
Right: filter.StringLiteral{Value: "bar"},
|
||||
},
|
||||
Right: filter.InfixExpression{
|
||||
Operator: filter.TokOpEq,
|
||||
Left: filter.StringLiteral{Value: "baz"},
|
||||
Right: filter.StringLiteral{Value: "yolo"},
|
||||
},
|
||||
},
|
||||
expected: `(("foo" = "bar") OR ("baz" = "yolo"))`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := tt.expression.String()
|
||||
if tt.expected != got {
|
||||
t.Errorf("expected: %v, got: %v", tt.expected, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool, error) {
|
||||
if headers == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch op {
|
||||
case TokOpEq:
|
||||
strLiteral, ok := expr.(StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("filter: expression must be a string literal")
|
||||
}
|
||||
|
||||
// Return `true` if at least one header (<key>: <value>) is equal to the string literal.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if strLiteral.Value == fmt.Sprintf("%v: %v", key, value) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
case TokOpNotEq:
|
||||
strLiteral, ok := expr.(StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("filter: expression must be a string literal")
|
||||
}
|
||||
|
||||
// Return `true` if none of the headers (<key>: <value>) are equal to the string literal.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if strLiteral.Value == fmt.Sprintf("%v: %v", key, value) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
case TokOpRe:
|
||||
re, ok := expr.(RegexpLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("filter: expression must be a regular expression")
|
||||
}
|
||||
|
||||
// Return `true` if at least one header (<key>: <value>) matches the regular expression.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if re.MatchString(fmt.Sprintf("%v: %v", key, value)) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
case TokOpNotRe:
|
||||
re, ok := expr.(RegexpLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("filter: expression must be a regular expression")
|
||||
}
|
||||
|
||||
// Return `true` if none of the headers (<key>: <value>) match the regular expression.
|
||||
for key, values := range headers {
|
||||
for _, value := range values {
|
||||
if re.MatchString(fmt.Sprintf("%v: %v", key, value)) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
default:
|
||||
return false, fmt.Errorf("filter: unsupported operator %q", op.String())
|
||||
}
|
||||
}
|
@ -11,10 +11,10 @@ import (
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/proxy/intercept"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
@ -59,17 +59,17 @@ type Settings struct {
|
||||
// Request log settings
|
||||
ReqLogBypassOutOfScope bool
|
||||
ReqLogOnlyFindInScope bool
|
||||
ReqLogSearchExpr filter.Expression
|
||||
ReqLogSearchExpr search.Expression
|
||||
|
||||
// Intercept settings
|
||||
InterceptRequests bool
|
||||
InterceptResponses bool
|
||||
InterceptRequestFilter filter.Expression
|
||||
InterceptResponseFilter filter.Expression
|
||||
InterceptRequestFilter search.Expression
|
||||
InterceptResponseFilter search.Expression
|
||||
|
||||
// Sender settings
|
||||
SenderOnlyFindInScope bool
|
||||
SenderSearchExpr filter.Expression
|
||||
SenderSearchExpr search.Expression
|
||||
|
||||
// Scope settings
|
||||
ScopeRules []scope.Rule
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
//nolint:unparam
|
||||
@ -68,22 +68,22 @@ var resFilterKeyFns = map[string]func(res *http.Response) (string, error){
|
||||
}
|
||||
|
||||
// MatchRequestFilter returns true if an HTTP request matches the request filter expression.
|
||||
func MatchRequestFilter(req *http.Request, expr filter.Expression) (bool, error) {
|
||||
func MatchRequestFilter(req *http.Request, expr search.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case filter.PrefixExpression:
|
||||
case search.PrefixExpression:
|
||||
return matchReqPrefixExpr(req, e)
|
||||
case filter.InfixExpression:
|
||||
case search.InfixExpression:
|
||||
return matchReqInfixExpr(req, e)
|
||||
case filter.StringLiteral:
|
||||
case search.StringLiteral:
|
||||
return matchReqStringLiteral(req, e)
|
||||
default:
|
||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func matchReqPrefixExpr(req *http.Request, expr filter.PrefixExpression) (bool, error) {
|
||||
func matchReqPrefixExpr(req *http.Request, expr search.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpNot:
|
||||
case search.TokOpNot:
|
||||
match, err := MatchRequestFilter(req, expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -95,9 +95,9 @@ func matchReqPrefixExpr(req *http.Request, expr filter.PrefixExpression) (bool,
|
||||
}
|
||||
}
|
||||
|
||||
func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, error) {
|
||||
func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpAnd:
|
||||
case search.TokOpAnd:
|
||||
left, err := MatchRequestFilter(req, expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -109,7 +109,7 @@ func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, er
|
||||
}
|
||||
|
||||
return left && right, nil
|
||||
case filter.TokOpOr:
|
||||
case search.TokOpOr:
|
||||
left, err := MatchRequestFilter(req, expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -123,7 +123,7 @@ func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, er
|
||||
return left || right, nil
|
||||
}
|
||||
|
||||
left, ok := expr.Left.(filter.StringLiteral)
|
||||
left, ok := expr.Left.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("left operand must be a string literal")
|
||||
}
|
||||
@ -133,30 +133,21 @@ func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, er
|
||||
return false, fmt.Errorf("failed to get string literal from request for left operand: %w", err)
|
||||
}
|
||||
|
||||
if leftVal == "headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
||||
right, ok := expr.Right.(search.RegexpLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a regular expression")
|
||||
}
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpRe:
|
||||
case search.TokOpRe:
|
||||
return right.MatchString(leftVal), nil
|
||||
case filter.TokOpNotRe:
|
||||
case search.TokOpNotRe:
|
||||
return !right.MatchString(leftVal), nil
|
||||
}
|
||||
}
|
||||
|
||||
right, ok := expr.Right.(filter.StringLiteral)
|
||||
right, ok := expr.Right.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a string literal")
|
||||
}
|
||||
@ -167,20 +158,20 @@ func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, er
|
||||
}
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpEq:
|
||||
case search.TokOpEq:
|
||||
return leftVal == rightVal, nil
|
||||
case filter.TokOpNotEq:
|
||||
case search.TokOpNotEq:
|
||||
return leftVal != rightVal, nil
|
||||
case filter.TokOpGt:
|
||||
case search.TokOpGt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal > rightVal, nil
|
||||
case filter.TokOpLt:
|
||||
case search.TokOpLt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal < rightVal, nil
|
||||
case filter.TokOpGtEq:
|
||||
case search.TokOpGtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal >= rightVal, nil
|
||||
case filter.TokOpLtEq:
|
||||
case search.TokOpLtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal <= rightVal, nil
|
||||
default:
|
||||
@ -197,18 +188,7 @@ func getMappedStringLiteralFromReq(req *http.Request, s string) (string, error)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func matchReqStringLiteral(req *http.Request, strLiteral filter.StringLiteral) (bool, error) {
|
||||
for key, values := range req.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchReqStringLiteral(req *http.Request, strLiteral search.StringLiteral) (bool, error) {
|
||||
for _, fn := range reqFilterKeyFns {
|
||||
value, err := fn(req)
|
||||
if err != nil {
|
||||
@ -279,22 +259,22 @@ func MatchRequestScope(req *http.Request, s *scope.Scope) (bool, error) {
|
||||
}
|
||||
|
||||
// MatchResponseFilter returns true if an HTTP response matches the response filter expression.
|
||||
func MatchResponseFilter(res *http.Response, expr filter.Expression) (bool, error) {
|
||||
func MatchResponseFilter(res *http.Response, expr search.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case filter.PrefixExpression:
|
||||
case search.PrefixExpression:
|
||||
return matchResPrefixExpr(res, e)
|
||||
case filter.InfixExpression:
|
||||
case search.InfixExpression:
|
||||
return matchResInfixExpr(res, e)
|
||||
case filter.StringLiteral:
|
||||
case search.StringLiteral:
|
||||
return matchResStringLiteral(res, e)
|
||||
default:
|
||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func matchResPrefixExpr(res *http.Response, expr filter.PrefixExpression) (bool, error) {
|
||||
func matchResPrefixExpr(res *http.Response, expr search.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpNot:
|
||||
case search.TokOpNot:
|
||||
match, err := MatchResponseFilter(res, expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -306,9 +286,9 @@ func matchResPrefixExpr(res *http.Response, expr filter.PrefixExpression) (bool,
|
||||
}
|
||||
}
|
||||
|
||||
func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, error) {
|
||||
func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpAnd:
|
||||
case search.TokOpAnd:
|
||||
left, err := MatchResponseFilter(res, expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -320,7 +300,7 @@ func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, e
|
||||
}
|
||||
|
||||
return left && right, nil
|
||||
case filter.TokOpOr:
|
||||
case search.TokOpOr:
|
||||
left, err := MatchResponseFilter(res, expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -334,7 +314,7 @@ func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, e
|
||||
return left || right, nil
|
||||
}
|
||||
|
||||
left, ok := expr.Left.(filter.StringLiteral)
|
||||
left, ok := expr.Left.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("left operand must be a string literal")
|
||||
}
|
||||
@ -344,30 +324,21 @@ func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, e
|
||||
return false, fmt.Errorf("failed to get string literal from response for left operand: %w", err)
|
||||
}
|
||||
|
||||
if leftVal == "headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, res.Header)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
||||
right, ok := expr.Right.(search.RegexpLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a regular expression")
|
||||
}
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpRe:
|
||||
case search.TokOpRe:
|
||||
return right.MatchString(leftVal), nil
|
||||
case filter.TokOpNotRe:
|
||||
case search.TokOpNotRe:
|
||||
return !right.MatchString(leftVal), nil
|
||||
}
|
||||
}
|
||||
|
||||
right, ok := expr.Right.(filter.StringLiteral)
|
||||
right, ok := expr.Right.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a string literal")
|
||||
}
|
||||
@ -378,20 +349,20 @@ func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, e
|
||||
}
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpEq:
|
||||
case search.TokOpEq:
|
||||
return leftVal == rightVal, nil
|
||||
case filter.TokOpNotEq:
|
||||
case search.TokOpNotEq:
|
||||
return leftVal != rightVal, nil
|
||||
case filter.TokOpGt:
|
||||
case search.TokOpGt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal > rightVal, nil
|
||||
case filter.TokOpLt:
|
||||
case search.TokOpLt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal < rightVal, nil
|
||||
case filter.TokOpGtEq:
|
||||
case search.TokOpGtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal >= rightVal, nil
|
||||
case filter.TokOpLtEq:
|
||||
case search.TokOpLtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal <= rightVal, nil
|
||||
default:
|
||||
@ -408,18 +379,7 @@ func getMappedStringLiteralFromRes(res *http.Response, s string) (string, error)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func matchResStringLiteral(res *http.Response, strLiteral filter.StringLiteral) (bool, error) {
|
||||
for key, values := range res.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchResStringLiteral(res *http.Response, strLiteral search.StringLiteral) (bool, error) {
|
||||
for _, fn := range resFilterKeyFns {
|
||||
value, err := fn(res)
|
||||
if err != nil {
|
||||
|
@ -10,9 +10,9 @@ import (
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/log"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -56,16 +56,16 @@ type Service struct {
|
||||
|
||||
requestsEnabled bool
|
||||
responsesEnabled bool
|
||||
reqFilter filter.Expression
|
||||
resFilter filter.Expression
|
||||
reqFilter search.Expression
|
||||
resFilter search.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Logger log.Logger
|
||||
RequestsEnabled bool
|
||||
ResponsesEnabled bool
|
||||
RequestFilter filter.Expression
|
||||
ResponseFilter filter.Expression
|
||||
RequestFilter search.Expression
|
||||
ResponseFilter search.Expression
|
||||
}
|
||||
|
||||
// RequestIDs implements sort.Interface.
|
||||
|
@ -1,10 +1,10 @@
|
||||
package intercept
|
||||
|
||||
import "github.com/dstotijn/hetty/pkg/filter"
|
||||
import "github.com/dstotijn/hetty/pkg/search"
|
||||
|
||||
type Settings struct {
|
||||
RequestsEnabled bool
|
||||
ResponsesEnabled bool
|
||||
RequestFilter filter.Expression
|
||||
ResponseFilter filter.Expression
|
||||
RequestFilter search.Expression
|
||||
ResponseFilter search.Expression
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ import (
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/log"
|
||||
"github.com/dstotijn/hetty/pkg/proxy"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
@ -77,7 +77,7 @@ type service struct {
|
||||
type FindRequestsFilter struct {
|
||||
ProjectID ulid.ULID
|
||||
OnlyInScope bool
|
||||
SearchExpr filter.Expression
|
||||
SearchExpr search.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{
|
||||
@ -36,22 +36,22 @@ var ResLogSearchKeyFns = map[string]func(rl ResponseLog) string{
|
||||
// TODO: Request and response headers search key functions.
|
||||
|
||||
// Matches returns true if the supplied search expression evaluates to true.
|
||||
func (reqLog RequestLog) Matches(expr filter.Expression) (bool, error) {
|
||||
func (reqLog RequestLog) Matches(expr search.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case filter.PrefixExpression:
|
||||
case search.PrefixExpression:
|
||||
return reqLog.matchPrefixExpr(e)
|
||||
case filter.InfixExpression:
|
||||
case search.InfixExpression:
|
||||
return reqLog.matchInfixExpr(e)
|
||||
case filter.StringLiteral:
|
||||
case search.StringLiteral:
|
||||
return reqLog.matchStringLiteral(e)
|
||||
default:
|
||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
func (reqLog RequestLog) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpNot:
|
||||
case search.TokOpNot:
|
||||
match, err := reqLog.Matches(expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -63,9 +63,9 @@ func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, er
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpAnd:
|
||||
case search.TokOpAnd:
|
||||
left, err := reqLog.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -77,7 +77,7 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
}
|
||||
|
||||
return left && right, nil
|
||||
case filter.TokOpOr:
|
||||
case search.TokOpOr:
|
||||
left, err := reqLog.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -91,46 +91,28 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
return left || right, nil
|
||||
}
|
||||
|
||||
left, ok := expr.Left.(filter.StringLiteral)
|
||||
left, ok := expr.Left.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("left operand must be a string literal")
|
||||
}
|
||||
|
||||
leftVal := reqLog.getMappedStringLiteral(left.Value)
|
||||
|
||||
if leftVal == "req.headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Header)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
if leftVal == "res.headers" && reqLog.Response != nil {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Header)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
||||
right, ok := expr.Right.(search.RegexpLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a regular expression")
|
||||
}
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpRe:
|
||||
case search.TokOpRe:
|
||||
return right.MatchString(leftVal), nil
|
||||
case filter.TokOpNotRe:
|
||||
case search.TokOpNotRe:
|
||||
return !right.MatchString(leftVal), nil
|
||||
}
|
||||
}
|
||||
|
||||
right, ok := expr.Right.(filter.StringLiteral)
|
||||
right, ok := expr.Right.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a string literal")
|
||||
}
|
||||
@ -138,20 +120,20 @@ func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, erro
|
||||
rightVal := reqLog.getMappedStringLiteral(right.Value)
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpEq:
|
||||
case search.TokOpEq:
|
||||
return leftVal == rightVal, nil
|
||||
case filter.TokOpNotEq:
|
||||
case search.TokOpNotEq:
|
||||
return leftVal != rightVal, nil
|
||||
case filter.TokOpGt:
|
||||
case search.TokOpGt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal > rightVal, nil
|
||||
case filter.TokOpLt:
|
||||
case search.TokOpLt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal < rightVal, nil
|
||||
case filter.TokOpGtEq:
|
||||
case search.TokOpGtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal >= rightVal, nil
|
||||
case filter.TokOpLtEq:
|
||||
case search.TokOpLtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal <= rightVal, nil
|
||||
default:
|
||||
@ -180,18 +162,7 @@ func (reqLog RequestLog) getMappedStringLiteral(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for key, values := range reqLog.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (reqLog RequestLog) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) {
|
||||
for _, fn := range reqLogSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(reqLog)),
|
||||
@ -202,17 +173,6 @@ func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bo
|
||||
}
|
||||
|
||||
if reqLog.Response != nil {
|
||||
for key, values := range reqLog.Response.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range ResLogSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(*reqLog.Response)),
|
||||
|
@ -3,8 +3,8 @@ package reqlog_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
func TestRequestLogMatch(t *testing.T) {
|
||||
@ -176,7 +176,7 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
searchExpr, err := filter.ParseQuery(tt.query)
|
||||
searchExpr, err := search.ParseQuery(tt.query)
|
||||
assertError(t, nil, err)
|
||||
|
||||
got, err := tt.requestLog.Matches(searchExpr)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package filter
|
||||
package search
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
@ -78,10 +78,8 @@ func (rl *RegexpLiteral) UnmarshalBinary(data []byte) error {
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The `filter` package was previously named `search`.
|
||||
// We use the legacy names for backwards compatibility with existing database data.
|
||||
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.PrefixExpression", PrefixExpression{})
|
||||
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.InfixExpression", InfixExpression{})
|
||||
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.StringLiteral", StringLiteral{})
|
||||
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.RegexpLiteral", RegexpLiteral{})
|
||||
gob.Register(PrefixExpression{})
|
||||
gob.Register(InfixExpression{})
|
||||
gob.Register(StringLiteral{})
|
||||
gob.Register(RegexpLiteral{})
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package filter
|
||||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package filter
|
||||
package search
|
||||
|
||||
import "testing"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package filter
|
||||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -88,7 +88,7 @@ func ParseQuery(input string) (expr Expression, err error) {
|
||||
p.nextToken()
|
||||
|
||||
if p.curTokenIs(TokEOF) {
|
||||
return nil, fmt.Errorf("filter: unexpected EOF")
|
||||
return nil, fmt.Errorf("search: unexpected EOF")
|
||||
}
|
||||
|
||||
for !p.curTokenIs(TokEOF) {
|
||||
@ -96,7 +96,7 @@ func ParseQuery(input string) (expr Expression, err error) {
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("filter: could not parse expression: %w", err)
|
||||
return nil, fmt.Errorf("search: could not parse expression: %w", err)
|
||||
case expr == nil:
|
||||
expr = right
|
||||
default:
|
@ -1,4 +1,4 @@
|
||||
package filter
|
||||
package search
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -20,7 +20,7 @@ func TestParseQuery(t *testing.T) {
|
||||
name: "empty query",
|
||||
input: "",
|
||||
expectedExpression: nil,
|
||||
expectedError: errors.New("filter: unexpected EOF"),
|
||||
expectedError: errors.New("search: unexpected EOF"),
|
||||
},
|
||||
{
|
||||
name: "string literal expression",
|
@ -7,9 +7,9 @@ import (
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
var senderReqSearchKeyFns = map[string]func(req Request) string{
|
||||
@ -29,22 +29,22 @@ var senderReqSearchKeyFns = map[string]func(req Request) string{
|
||||
// TODO: Request and response headers search key functions.
|
||||
|
||||
// Matches returns true if the supplied search expression evaluates to true.
|
||||
func (req Request) Matches(expr filter.Expression) (bool, error) {
|
||||
func (req Request) Matches(expr search.Expression) (bool, error) {
|
||||
switch e := expr.(type) {
|
||||
case filter.PrefixExpression:
|
||||
case search.PrefixExpression:
|
||||
return req.matchPrefixExpr(e)
|
||||
case filter.InfixExpression:
|
||||
case search.InfixExpression:
|
||||
return req.matchInfixExpr(e)
|
||||
case filter.StringLiteral:
|
||||
case search.StringLiteral:
|
||||
return req.matchStringLiteral(e)
|
||||
default:
|
||||
return false, fmt.Errorf("expression type (%T) not supported", expr)
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
func (req Request) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpNot:
|
||||
case search.TokOpNot:
|
||||
match, err := req.Matches(expr.Right)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -56,9 +56,9 @@ func (req Request) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
|
||||
switch expr.Operator {
|
||||
case filter.TokOpAnd:
|
||||
case search.TokOpAnd:
|
||||
left, err := req.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -70,7 +70,7 @@ func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
}
|
||||
|
||||
return left && right, nil
|
||||
case filter.TokOpOr:
|
||||
case search.TokOpOr:
|
||||
left, err := req.Matches(expr.Left)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -84,46 +84,28 @@ func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
return left || right, nil
|
||||
}
|
||||
|
||||
left, ok := expr.Left.(filter.StringLiteral)
|
||||
left, ok := expr.Left.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("left operand must be a string literal")
|
||||
}
|
||||
|
||||
leftVal := req.getMappedStringLiteral(left.Value)
|
||||
|
||||
if leftVal == "req.headers" {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
if leftVal == "res.headers" && req.Response != nil {
|
||||
match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Response.Header)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
|
||||
}
|
||||
|
||||
return match, nil
|
||||
}
|
||||
|
||||
if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
|
||||
right, ok := expr.Right.(filter.RegexpLiteral)
|
||||
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe {
|
||||
right, ok := expr.Right.(search.RegexpLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a regular expression")
|
||||
}
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpRe:
|
||||
case search.TokOpRe:
|
||||
return right.MatchString(leftVal), nil
|
||||
case filter.TokOpNotRe:
|
||||
case search.TokOpNotRe:
|
||||
return !right.MatchString(leftVal), nil
|
||||
}
|
||||
}
|
||||
|
||||
right, ok := expr.Right.(filter.StringLiteral)
|
||||
right, ok := expr.Right.(search.StringLiteral)
|
||||
if !ok {
|
||||
return false, errors.New("right operand must be a string literal")
|
||||
}
|
||||
@ -131,20 +113,20 @@ func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
|
||||
rightVal := req.getMappedStringLiteral(right.Value)
|
||||
|
||||
switch expr.Operator {
|
||||
case filter.TokOpEq:
|
||||
case search.TokOpEq:
|
||||
return leftVal == rightVal, nil
|
||||
case filter.TokOpNotEq:
|
||||
case search.TokOpNotEq:
|
||||
return leftVal != rightVal, nil
|
||||
case filter.TokOpGt:
|
||||
case search.TokOpGt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal > rightVal, nil
|
||||
case filter.TokOpLt:
|
||||
case search.TokOpLt:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal < rightVal, nil
|
||||
case filter.TokOpGtEq:
|
||||
case search.TokOpGtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal >= rightVal, nil
|
||||
case filter.TokOpLtEq:
|
||||
case search.TokOpLtEq:
|
||||
// TODO(?) attempt to parse as int.
|
||||
return leftVal <= rightVal, nil
|
||||
default:
|
||||
@ -173,18 +155,7 @@ func (req Request) getMappedStringLiteral(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (req Request) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
|
||||
for key, values := range req.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (req Request) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) {
|
||||
for _, fn := range senderReqSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(req)),
|
||||
@ -195,17 +166,6 @@ func (req Request) matchStringLiteral(strLiteral filter.StringLiteral) (bool, er
|
||||
}
|
||||
|
||||
if req.Response != nil {
|
||||
for key, values := range req.Response.Header {
|
||||
for _, value := range values {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fmt.Sprintf("%v: %v", key, value)),
|
||||
strings.ToLower(strLiteral.Value),
|
||||
) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range reqlog.ResLogSearchKeyFns {
|
||||
if strings.Contains(
|
||||
strings.ToLower(fn(*req.Response)),
|
||||
|
@ -3,8 +3,8 @@ package sender_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
"github.com/dstotijn/hetty/pkg/sender"
|
||||
)
|
||||
|
||||
@ -177,7 +177,7 @@ func TestRequestLogMatch(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
searchExpr, err := filter.ParseQuery(tt.query)
|
||||
searchExpr, err := search.ParseQuery(tt.query)
|
||||
assertError(t, nil, err)
|
||||
|
||||
got, err := tt.senderReq.Matches(searchExpr)
|
||||
|
@ -12,9 +12,9 @@ import (
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
|
||||
"github.com/dstotijn/hetty/pkg/filter"
|
||||
"github.com/dstotijn/hetty/pkg/reqlog"
|
||||
"github.com/dstotijn/hetty/pkg/scope"
|
||||
"github.com/dstotijn/hetty/pkg/search"
|
||||
)
|
||||
|
||||
//nolint:gosec
|
||||
@ -54,7 +54,7 @@ type service struct {
|
||||
type FindRequestsFilter struct {
|
||||
ProjectID ulid.ULID
|
||||
OnlyInScope bool
|
||||
SearchExpr filter.Expression
|
||||
SearchExpr search.Expression
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
Reference in New Issue
Block a user