Skip to content
This repository has been archived by the owner on Jan 31, 2024. It is now read-only.

Commit

Permalink
Allow http service to expose prefixes containing slashes (/) (cs3org#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gmgigi96 committed Jul 14, 2022
1 parent 57a46b2 commit b880a06
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 10 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/ext-http-service-prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Allow http service to expose prefixes containing /

https://github.com/cs3org/reva/pull/3070
75 changes: 65 additions & 10 deletions pkg/rhttp/rhttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ import (
"net/http"
"path"
"sort"
"strings"
"time"

"github.com/cs3org/reva/internal/http/interceptors/appctx"
"github.com/cs3org/reva/internal/http/interceptors/auth"
"github.com/cs3org/reva/internal/http/interceptors/log"
"github.com/cs3org/reva/internal/http/interceptors/providerauthorizer"
"github.com/cs3org/reva/pkg/rhttp/global"
"github.com/cs3org/reva/pkg/rhttp/router"
rtrace "github.com/cs3org/reva/pkg/trace"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
Expand Down Expand Up @@ -234,25 +234,80 @@ func getUnprotected(prefix string, unprotected []string) []string {
return unprotected
}

// clean the url putting a slash (/) at the beginning if it does not have it
// and removing the slashes at the end
// if the url is "/", the output is ""
func cleanURL(url string) string {
if len(url) > 0 {
if url[0] != '/' {
url = "/" + url
}
url = strings.TrimRight(url, "/")
}
return url
}

func urlHasPrefix(url, prefix string) bool {
url = cleanURL(url)
prefix = cleanURL(prefix)

partsURL := strings.Split(url, "/")
partsPrefix := strings.Split(prefix, "/")

if len(partsPrefix) > len(partsURL) {
return false
}

for i, p := range partsPrefix {
u := partsURL[i]
if p != u {
return false
}
}

return true
}

func (s *Server) getHandlerLongestCommongURL(url string) (http.Handler, string, bool) {
var match string

for k := range s.handlers {
if urlHasPrefix(url, k) && len(k) > len(match) {
match = k
}
}

h, ok := s.handlers[match]
return h, match, ok
}

func getSubURL(url, prefix string) string {
// pre cond: prefix is a prefix for url
// example: url = "/api/v0/", prefix = "/api", res = "/v0"
url = cleanURL(url)
prefix = cleanURL(prefix)

return url[len(prefix):]
}

func (s *Server) getHandler() (http.Handler, error) {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
head, tail := router.ShiftPath(r.URL.Path)
if h, ok := s.handlers[head]; ok {
r.URL.Path = tail
s.log.Debug().Msgf("http routing: head=%s tail=%s svc=%s", head, r.URL.Path, head)
if h, ok := s.handlers[r.URL.Path]; ok {
s.log.Debug().Msgf("http routing: url=%s", r.URL.Path)
r.URL.Path = "/"
h.ServeHTTP(w, r)
return
}

// when a service is exposed at the root.
if h, ok := s.handlers[""]; ok {
r.URL.Path = "/" + head + tail
s.log.Debug().Msgf("http routing: head= tail=%s svc=root", r.URL.Path)
// find by longest common path
if h, url, ok := s.getHandlerLongestCommongURL(r.URL.Path); ok {
s.log.Debug().Msgf("http routing: url=%s", url)
r.URL.Path = getSubURL(r.URL.Path, url)
h.ServeHTTP(w, r)
return
}

s.log.Debug().Msgf("http routing: head=%s tail=%s svc=not-found", head, tail)
s.log.Debug().Msgf("http routing: url=%s svc=not-found", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
})

Expand Down
74 changes: 74 additions & 0 deletions pkg/rhttp/rhttp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2018-2022 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package rhttp

import "testing"

func TestURLHasPrefix(t *testing.T) {
tests := map[string]struct {
url string
prefix string
expected bool
}{
"root": {
url: "/",
prefix: "/",
expected: true,
},
"suburl_root": {
url: "/api/v0",
prefix: "/",
expected: true,
},
"suburl_root_slash_end": {
url: "/api/v0/",
prefix: "/",
expected: true,
},
"suburl_root_no_slash": {
url: "/api/v0",
prefix: "",
expected: true,
},
"no_common_prefix": {
url: "/api/v0/project",
prefix: "/api/v0/p",
expected: false,
},
"long_url_prefix": {
url: "/api/v0/project/test",
prefix: "/api/v0",
expected: true,
},
"prefix_end_slash": {
url: "/api/v0/project/test",
prefix: "/api/v0/",
expected: true,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
res := urlHasPrefix(test.url, test.prefix)
if res != test.expected {
t.Fatalf("%s got an unexpected result: %+v instead of %+v", t.Name(), res, test.expected)
}
})
}
}

0 comments on commit b880a06

Please sign in to comment.