diff --git a/api/v1/redirector.go b/api/v1/redirector.go index a376387..867dc02 100644 --- a/api/v1/redirector.go +++ b/api/v1/redirector.go @@ -3,6 +3,7 @@ package v1 import ( "encoding/json" "net/http" + "net/url" "github.com/boojack/shortify/store" "github.com/labstack/echo/v4" @@ -39,7 +40,10 @@ func (s *APIV1Service) registerRedirectorRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err) } - return c.Redirect(http.StatusSeeOther, shortcut.Link) + if isValidURLString(shortcut.Link) { + return c.Redirect(http.StatusSeeOther, shortcut.Link) + } + return c.String(http.StatusOK, shortcut.Link) }) } @@ -66,3 +70,8 @@ func (s *APIV1Service) createShortcutViewActivity(c echo.Context, shortcut *stor } return nil } + +func isValidURLString(s string) bool { + _, err := url.ParseRequestURI(s) + return err == nil +} diff --git a/api/v1/redirector_test.go b/api/v1/redirector_test.go new file mode 100644 index 0000000..1e988a6 --- /dev/null +++ b/api/v1/redirector_test.go @@ -0,0 +1,33 @@ +package v1 + +import "testing" + +func TestIsValidURLString(t *testing.T) { + tests := []struct { + link string + expected bool + }{ + { + link: "https://google.com", + expected: true, + }, + { + link: "http://google.com", + expected: true, + }, + { + link: "google.com", + expected: false, + }, + { + link: "mailto:email@example.com", + expected: true, + }, + } + + for _, test := range tests { + if isValidURLString(test.link) != test.expected { + t.Errorf("isValidURLString(%s) = %v, expected %v", test.link, !test.expected, test.expected) + } + } +} diff --git a/api/v1/shortcut.go b/api/v1/shortcut.go index dcc114a..9e2035f 100644 --- a/api/v1/shortcut.go +++ b/api/v1/shortcut.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" - "github.com/boojack/shortify/internal/util" "github.com/boojack/shortify/store" "github.com/pkg/errors" @@ -86,9 +85,6 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { if err := json.NewDecoder(c.Request().Body).Decode(create); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err) } - if !validateLink(create.Link) { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid link: %s", create.Link)) - } shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{ CreatorID: userID, @@ -151,9 +147,6 @@ func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) { name := strings.ToLower(*patch.Name) patch.Name = &name } - if patch.Link != nil && !validateLink(*patch.Link) { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid link: %s", *patch.Link)) - } shortcutUpdate := &store.UpdateShortcut{ ID: shortcutID, @@ -369,7 +362,3 @@ func convertShortcutFromStore(shortcut *store.Shortcut) *Shortcut { Tags: tags, } } - -func validateLink(link string) bool { - return util.HasPrefixes(link, "http://", "https://") -} diff --git a/web/src/components/ShortcutView.tsx b/web/src/components/ShortcutView.tsx index 25a2ad1..9883558 100644 --- a/web/src/components/ShortcutView.tsx +++ b/web/src/components/ShortcutView.tsx @@ -24,6 +24,7 @@ const ShortcutView = (props: Props) => { const faviconStore = useFaviconStore(); const [favicon, setFavicon] = useState(undefined); const havePermission = user.role === "ADMIN" || shortcut.creatorId === user.id; + const shortifyLink = absolutifyLink(`/s/${shortcut.name}`); useEffect(() => { faviconStore.getOrFetchUrlFavicon(shortcut.link).then((url) => { @@ -33,8 +34,8 @@ const ShortcutView = (props: Props) => { }); }, [shortcut.link]); - const handleCopyButtonClick = (shortcut: Shortcut) => { - copy(absolutifyLink(`/s/${shortcut.name}`)); + const handleCopyButtonClick = () => { + copy(shortifyLink); toast.success("Shortcut link copied to clipboard."); }; @@ -60,11 +61,11 @@ const ShortcutView = (props: Props) => { )} - - +