001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.support.sentinel;
017
018import com.alibaba.csp.sentinel.util.StringUtil;
019import com.jfinal.kit.JsonKit;
020import com.jfinal.kit.LogKit;
021
022import javax.servlet.http.HttpServletRequest;
023import javax.servlet.http.HttpServletResponse;
024import java.io.IOException;
025import java.io.PrintWriter;
026import java.util.Map;
027
028/**
029 * @author michael yang (fuhai999@gmail.com)
030 * @Date: 2020/3/22
031 */
032public class SentinelUtil {
033
034    private static final String PATH_SPLIT = "/";
035
036    public static String buildResource(HttpServletRequest request) {
037        String pathInfo = getResourcePath(request);
038        if (!pathInfo.startsWith(PATH_SPLIT)) {
039            pathInfo = PATH_SPLIT + pathInfo;
040        }
041
042        if (PATH_SPLIT.equals(pathInfo)) {
043            return pathInfo;
044        }
045
046        // Note: pathInfo should be converted to camelCase style.
047        int lastSlashIndex = pathInfo.lastIndexOf("/");
048
049        if (lastSlashIndex >= 0) {
050            pathInfo = pathInfo.substring(0, lastSlashIndex) + "/"
051                    + StringUtil.trim(pathInfo.substring(lastSlashIndex + 1));
052        } else {
053            pathInfo = PATH_SPLIT + StringUtil.trim(pathInfo);
054        }
055
056        return pathInfo;
057    }
058
059
060    private static String getResourcePath(HttpServletRequest request) {
061        String pathInfo = normalizeAbsolutePath(request.getPathInfo(), false);
062        String servletPath = normalizeAbsolutePath(request.getServletPath(), pathInfo.length() != 0);
063
064        return servletPath + pathInfo;
065    }
066
067
068    private static String normalizeAbsolutePath(String path, boolean removeTrailingSlash) throws IllegalStateException {
069        return normalizePath(path, true, false, removeTrailingSlash);
070    }
071
072
073    private static String normalizePath(String path, boolean forceAbsolute, boolean forceRelative,
074                                        boolean removeTrailingSlash) throws IllegalStateException {
075        char[] pathChars = StringUtil.trimToEmpty(path).toCharArray();
076        int length = pathChars.length;
077
078        // Check path and slash.
079        boolean startsWithSlash = false;
080        boolean endsWithSlash = false;
081
082        if (length > 0) {
083            char firstChar = pathChars[0];
084            char lastChar = pathChars[length - 1];
085
086            startsWithSlash = firstChar == PATH_SPLIT.charAt(0) || firstChar == '\\';
087            endsWithSlash = lastChar == PATH_SPLIT.charAt(0) || lastChar == '\\';
088        }
089
090        StringBuilder buf = new StringBuilder(length);
091        boolean isAbsolutePath = forceAbsolute || !forceRelative && startsWithSlash;
092        int index = startsWithSlash ? 0 : -1;
093        int level = 0;
094
095        if (isAbsolutePath) {
096            buf.append(PATH_SPLIT);
097        }
098
099        while (index < length) {
100            index = indexOfSlash(pathChars, index + 1, false);
101
102            if (index == length) {
103                break;
104            }
105
106            int nextSlashIndex = indexOfSlash(pathChars, index, true);
107
108            String element = new String(pathChars, index, nextSlashIndex - index);
109            index = nextSlashIndex;
110
111            // Ignore "."
112            if (".".equals(element)) {
113                continue;
114            }
115
116            // Backtrack ".."
117            if ("..".equals(element)) {
118                if (level == 0) {
119                    if (isAbsolutePath) {
120                        throw new IllegalStateException(path);
121                    } else {
122                        buf.append("..").append(PATH_SPLIT);
123                    }
124                } else {
125                    buf.setLength(pathChars[--level]);
126                }
127
128                continue;
129            }
130
131            pathChars[level++] = (char) buf.length();
132            buf.append(element).append(PATH_SPLIT);
133        }
134
135        // remove the last "/"
136        if (buf.length() > 0) {
137            if (!endsWithSlash || removeTrailingSlash) {
138                buf.setLength(buf.length() - 1);
139            }
140        }
141
142        return buf.toString();
143    }
144
145
146    private static int indexOfSlash(char[] chars, int beginIndex, boolean slash) {
147        int i = beginIndex;
148
149        for (; i < chars.length; i++) {
150            char ch = chars[i];
151
152            if (slash) {
153                if (ch == PATH_SPLIT.charAt(0) || ch == '\\') {
154                    break; // if a slash
155                }
156            } else {
157                if (ch != PATH_SPLIT.charAt(0) && ch != '\\') {
158                    break; // if not a slash
159                }
160            }
161        }
162
163        return i;
164    }
165
166
167    protected static final String contentType = "application/json; charset=utf-8";
168
169    public static void writeDefaultBlockedJson(HttpServletResponse resp, Map map) throws IOException {
170        resp.setStatus(200);
171        resp.setContentType(contentType);
172        try (PrintWriter out = resp.getWriter()) {
173            out.print(JsonKit.toJson(map));
174        }
175    }
176
177
178    public static void writeDefaultBlockedPage(HttpServletResponse resp) throws IOException {
179        resp.setStatus(200);
180        try (PrintWriter out = resp.getWriter()) {
181            out.print("Blocked by Sentinel (flow limiting) in Jboot");
182        }
183    }
184
185
186    public static void blockRequest(HttpServletRequest request, HttpServletResponse response) {
187        StringBuffer url = request.getRequestURL();
188
189        if ("GET".equals(request.getMethod()) && StringUtil.isNotBlank(request.getQueryString())) {
190            url.append("?").append(request.getQueryString());
191        }
192
193        SentinelConfig config = SentinelConfig.get();
194
195        try {
196            if (StringUtil.isNotBlank(config.getRequestBlockPage())) {
197                String redirectUrl = config.getRequestBlockPage() + "?http_referer=" + url.toString();
198                response.sendRedirect(redirectUrl);
199            } else if (config.getRequestBlockJsonMap() != null && !config.getRequestBlockJsonMap().isEmpty()) {
200                writeDefaultBlockedJson(response, config.getRequestBlockJsonMap());
201            } else {
202                writeDefaultBlockedPage(response);
203            }
204        } catch (IOException ex) {
205            LogKit.error(ex.toString(), ex);
206        }
207    }
208
209}