BAEL-7200
Modify Request Body Before Reaching Controller in Spring Boot
This commit is contained in:
		
							parent
							
								
									336ee922eb
								
							
						
					
					
						commit
						f90651c5f9
					
				| @ -49,6 +49,10 @@ | ||||
|             <artifactId>commons-configuration</artifactId> | ||||
|             <version>${commons-configuration.version}</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-starter-aop</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
|     <build> | ||||
| @ -61,6 +65,14 @@ | ||||
|                     <layout>JAR</layout> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-compiler-plugin</artifactId> | ||||
|                 <configuration> | ||||
|                     <source>9</source> | ||||
|                     <target>9</target> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|         </plugins> | ||||
|     </build> | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,11 @@ | ||||
| package com.baeldung.modifyrequest; | ||||
| 
 | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| 
 | ||||
| @SpringBootApplication(scanBasePackages = "com.baeldung.modifyrequest") | ||||
| public class ModifyRequestApp { | ||||
|     public static void main(String[] args) { | ||||
|         SpringApplication.run(ModifyRequestApp.class, args); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,74 @@ | ||||
| package com.baeldung.modifyrequest.aop; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.context.annotation.Profile; | ||||
| import org.springframework.core.MethodParameter; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.HttpInputMessage; | ||||
| import org.springframework.http.converter.HttpMessageConverter; | ||||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice; | ||||
| 
 | ||||
| import java.io.*; | ||||
| import java.lang.reflect.Type; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| 
 | ||||
| @RestControllerAdvice | ||||
| @Profile("aspectExample") | ||||
| public class EscapeHtmlAspect implements RequestBodyAdvice { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(EscapeHtmlAspect.class); | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { | ||||
|         //Apply this to all Controllers | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { | ||||
|         logger.info("beforeBodyRead called"); | ||||
|         InputStream inputStream = inputMessage.getBody(); | ||||
|         return new HttpInputMessage() { | ||||
|             @Override | ||||
|             public InputStream getBody() throws IOException { | ||||
|                 return new ByteArrayInputStream(escapeHtml(inputStream).getBytes(StandardCharsets.UTF_8)); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public HttpHeaders getHeaders() { | ||||
|                 return inputMessage.getHeaders(); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { | ||||
|         // Return the modified object after reading the body | ||||
|         return body; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { | ||||
|         //return the original body | ||||
|         return body; | ||||
|     } | ||||
| 
 | ||||
|     private String escapeHtml(InputStream inputStream) throws IOException { | ||||
|         StringBuilder stringBuilder = new StringBuilder(); | ||||
|         BufferedReader bufferedReader = null; | ||||
|         try (inputStream) { | ||||
|             bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); | ||||
|             char[] charBuffer = new char[128]; | ||||
|             int bytesRead = -1; | ||||
|             while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { | ||||
|                 stringBuilder.append(charBuffer, 0, bytesRead); | ||||
|             } | ||||
|         } | ||||
|         String input = stringBuilder.toString(); | ||||
|         // Escape HTML characters | ||||
|         return input.replaceAll("&", "&") | ||||
|                 .replaceAll("<", "<") | ||||
|                 .replaceAll(">", ">"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| package com.baeldung.modifyrequest.config; | ||||
| 
 | ||||
| import com.baeldung.modifyrequest.interceptor.EscapeHtmlRequestInterceptor; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Profile; | ||||
| import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
| 
 | ||||
| @Configuration | ||||
| @Profile("interceptorExample") | ||||
| public class WebMvcConfiguration implements WebMvcConfigurer { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(WebMvcConfiguration.class); | ||||
| 
 | ||||
|     @Override | ||||
|     public void addInterceptors(InterceptorRegistry registry) { | ||||
|         logger.info("addInterceptors() called"); | ||||
|         registry.addInterceptor(new EscapeHtmlRequestInterceptor()) | ||||
|                 .addPathPatterns("/save"); | ||||
| 
 | ||||
|         WebMvcConfigurer.super.addInterceptors(registry); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -0,0 +1,23 @@ | ||||
| package com.baeldung.modifyrequest.controller; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| @RestController | ||||
| @RequestMapping("/") | ||||
| public class UserController { | ||||
|     Logger logger = LoggerFactory.getLogger(UserController.class); | ||||
| 
 | ||||
|     @PostMapping(value = "save") | ||||
|     public ResponseEntity<String> saveUser(@RequestBody String user) { | ||||
|         logger.info("save user info into database"); | ||||
|         ResponseEntity<String> responseEntity = new ResponseEntity<>(user, HttpStatus.CREATED); | ||||
|         return responseEntity; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| package com.baeldung.modifyrequest.filter; | ||||
| 
 | ||||
| import com.baeldung.modifyrequest.requestwrapper.EscapeHtmlRequestWrapper; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.context.annotation.Profile; | ||||
| import org.springframework.core.annotation.Order; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import javax.servlet.*; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| @Component | ||||
| @Order(1) | ||||
| @Profile("filterExample") | ||||
| public class EscapeHtmlFilter implements Filter { | ||||
|     Logger logger = LoggerFactory.getLogger(EscapeHtmlFilter.class); | ||||
| 
 | ||||
|     @Override | ||||
|     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) | ||||
|             throws IOException, ServletException { | ||||
|         logger.info("Modify the request"); | ||||
| 
 | ||||
|         filterChain.doFilter(new EscapeHtmlRequestWrapper((HttpServletRequest) servletRequest), servletResponse); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| package com.baeldung.modifyrequest.interceptor; | ||||
| 
 | ||||
| import com.baeldung.modifyrequest.requestwrapper.EscapeHtmlRequestWrapper; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.web.servlet.HandlerInterceptor; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| public class EscapeHtmlRequestInterceptor implements HandlerInterceptor { | ||||
|     private static final Logger logger = LoggerFactory.getLogger(EscapeHtmlRequestInterceptor.class); | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | ||||
|         EscapeHtmlRequestWrapper escapeHtmlRequestWrapper = new EscapeHtmlRequestWrapper(request); | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,68 @@ | ||||
| package com.baeldung.modifyrequest.requestwrapper; | ||||
| 
 | ||||
| import javax.servlet.ReadListener; | ||||
| import javax.servlet.ServletInputStream; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletRequestWrapper; | ||||
| import java.io.*; | ||||
| 
 | ||||
| public class EscapeHtmlRequestWrapper extends HttpServletRequestWrapper { | ||||
|     private String body = null; | ||||
|     public EscapeHtmlRequestWrapper(HttpServletRequest request) throws IOException { | ||||
|         super(request); | ||||
|         this.body = this.escapeHtml(request); | ||||
|     } | ||||
| 
 | ||||
|     private String escapeHtml(HttpServletRequest request) throws IOException { | ||||
|         StringBuilder stringBuilder = new StringBuilder(); | ||||
|         BufferedReader bufferedReader = null; | ||||
|         try (InputStream inputStream = request.getInputStream()) { | ||||
|             bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); | ||||
|             char[] charBuffer = new char[128]; | ||||
|             int bytesRead = -1; | ||||
|             while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { | ||||
|                 stringBuilder.append(charBuffer, 0, bytesRead); | ||||
|             } | ||||
|         } | ||||
|         String input = stringBuilder.toString(); | ||||
|         // Escape HTML characters | ||||
|         return input.replaceAll("&", "&") | ||||
|                 .replaceAll("<", "<") | ||||
|                 .replaceAll(">", ">") | ||||
|                 //.replaceAll("\"", """) | ||||
|                 .replaceAll("'", "'"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ServletInputStream getInputStream() { | ||||
|         final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); | ||||
|         ServletInputStream servletInputStream = new ServletInputStream() { | ||||
| 
 | ||||
|             @Override | ||||
|             public int read() { | ||||
|                 return byteArrayInputStream.read(); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public boolean isFinished() { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public boolean isReady() { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void setReadListener(ReadListener listener) { | ||||
| 
 | ||||
|             } | ||||
|         }; | ||||
|         return servletInputStream; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public BufferedReader getReader() throws IOException { | ||||
|         return new BufferedReader(new InputStreamReader(this.getInputStream())); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| @startuml | ||||
| 'https://plantuml.com/sequence-diagram | ||||
| skinparam sequenceMessageAlign direction | ||||
| skinparam handwritten true | ||||
| skinparam sequence { | ||||
| ParticipantBackgroundColor beige | ||||
| ParticipantPadding 50 | ||||
| } | ||||
| 
 | ||||
| autonumber | ||||
| 
 | ||||
| Browser -[#63b175]> Filter: HTTP Request | ||||
| activate Browser | ||||
| activate Filter | ||||
| Filter -[#63b175]> Filter: doFilter() | ||||
| Filter -[#63b175]> DispatcherServlet: HTTP Request | ||||
| activate DispatcherServlet | ||||
| 
 | ||||
| 
 | ||||
| DispatcherServlet -[#63b175]> Controller: HTTP Request | ||||
| activate Controller | ||||
| Controller --[#63b175]> DispatcherServlet: HTTP Response | ||||
| deactivate Controller | ||||
| 
 | ||||
| DispatcherServlet --[#63b175]> Filter: HTTP Response | ||||
| deactivate DispatcherServlet | ||||
| 
 | ||||
| Filter --[#63b175]> Browser: HTTP Response | ||||
| deactivate Filter | ||||
| deactivate Browser | ||||
| @enduml | ||||
| @ -0,0 +1,33 @@ | ||||
| @startuml | ||||
| 'https://plantuml.com/sequence-diagram | ||||
| skinparam sequenceMessageAlign direction | ||||
| skinparam handwritten true | ||||
| skinparam sequence { | ||||
| ParticipantBackgroundColor beige | ||||
| ParticipantPadding 50 | ||||
| } | ||||
| 
 | ||||
| autonumber | ||||
| 
 | ||||
| Browser -[#63b175]> Filter: Http Request | ||||
| activate Browser | ||||
| activate Filter | ||||
| Filter -[#63b175]> DispatcherServlet: Http Request | ||||
| activate DispatcherServlet | ||||
| 
 | ||||
| DispatcherServlet -[#63b175]> Interceptor: Http Request | ||||
| activate Interceptor | ||||
| Interceptor -[#63b175]> Interceptor: preHandle() | ||||
| Interceptor -[#63b175]> Controller: Http Request | ||||
| activate Controller | ||||
| Controller --[#63b175]> Interceptor: Http Response | ||||
| deactivate Controller | ||||
| Interceptor --[#63b175]> DispatcherServlet: Http Response | ||||
| deactivate Interceptor | ||||
| DispatcherServlet --[#63b175]> Filter: Http Response | ||||
| deactivate DispatcherServlet | ||||
| 
 | ||||
| Filter --[#63b175]> Browser: Http Response | ||||
| deactivate Filter | ||||
| deactivate Browser | ||||
| @enduml | ||||
| @ -0,0 +1,52 @@ | ||||
| package com.baeldung.modifyrequest; | ||||
| 
 | ||||
| import com.baeldung.modifyrequest.controller.UserController; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.extension.ExtendWith; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||||
| import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.junit.jupiter.SpringExtension; | ||||
| import org.springframework.test.web.servlet.MockMvc; | ||||
| import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; | ||||
| import org.springframework.test.web.servlet.result.MockMvcResultMatchers; | ||||
| 
 | ||||
| import java.net.URI; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| @ExtendWith(SpringExtension.class) | ||||
| @AutoConfigureMockMvc | ||||
| @WebMvcTest(UserController.class) | ||||
| @ActiveProfiles("aspectExample") | ||||
| public class EscapeHtmlAspectIntegrationTest { | ||||
|     Logger logger = LoggerFactory.getLogger(EscapeHtmlAspectIntegrationTest.class); | ||||
| 
 | ||||
|     @Autowired | ||||
|     private MockMvc mockMvc; | ||||
|     @Test | ||||
|     void givenAspect_whenEscapeHtmlAspect_thenEscapeHtml() throws Exception { | ||||
| 
 | ||||
|         Map<String, String> requestBody = Map.of( | ||||
|                 "name", "James Cameron", | ||||
|                 "email", "<script>alert()</script>james@gmail.com" | ||||
|         ); | ||||
| 
 | ||||
|         Map<String, String> expectedResponseBody = Map.of( | ||||
|                 "name", "James Cameron", | ||||
|                 "email", "<script>alert()</script>james@gmail.com" | ||||
|         ); | ||||
| 
 | ||||
|         ObjectMapper objectMapper = new ObjectMapper(); | ||||
| 
 | ||||
|         mockMvc.perform(MockMvcRequestBuilders.post(URI.create("/save")) | ||||
|           .contentType(MediaType.APPLICATION_JSON) | ||||
|           .content(objectMapper.writeValueAsString(requestBody))) | ||||
|           .andExpect(MockMvcResultMatchers.status().isCreated()) | ||||
|           .andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(expectedResponseBody))); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,51 @@ | ||||
| package com.baeldung.modifyrequest; | ||||
| 
 | ||||
| import com.baeldung.modifyrequest.controller.UserController; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.extension.ExtendWith; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||||
| import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.junit.jupiter.SpringExtension; | ||||
| import org.springframework.test.web.servlet.MockMvc; | ||||
| import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; | ||||
| import org.springframework.test.web.servlet.result.MockMvcResultMatchers; | ||||
| 
 | ||||
| import java.net.URI; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| @ExtendWith(SpringExtension.class) | ||||
| @AutoConfigureMockMvc | ||||
| @WebMvcTest(UserController.class) | ||||
| @ActiveProfiles("filterExample") | ||||
| public class EscapeHtmlFilterIntegrationTest { | ||||
|     Logger logger = LoggerFactory.getLogger(EscapeHtmlFilterIntegrationTest.class); | ||||
| 
 | ||||
|     @Autowired | ||||
|     private MockMvc mockMvc; | ||||
|     @Test | ||||
|     void givenFilter_whenEscapeHtmlFilter_thenEscapeHtml() throws Exception { | ||||
|         Map<String, String> requestBody = Map.of( | ||||
|                 "name", "James Cameron", | ||||
|                 "email", "<script>alert()</script>james@gmail.com" | ||||
|         ); | ||||
| 
 | ||||
|         Map<String, String> expectedResponseBody = Map.of( | ||||
|                 "name", "James Cameron", | ||||
|                 "email", "<script>alert()</script>james@gmail.com" | ||||
|         ); | ||||
| 
 | ||||
|         ObjectMapper objectMapper = new ObjectMapper(); | ||||
| 
 | ||||
|         mockMvc.perform(MockMvcRequestBuilders.post(URI.create("/save")) | ||||
|           .contentType(MediaType.APPLICATION_JSON) | ||||
|           .content(objectMapper.writeValueAsString(requestBody))) | ||||
|           .andExpect(MockMvcResultMatchers.status().isCreated()) | ||||
|           .andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(expectedResponseBody))); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package com.baeldung.modifyrequest; | ||||
| 
 | ||||
| import com.baeldung.modifyrequest.controller.UserController; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.extension.ExtendWith; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||||
| import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.junit.jupiter.SpringExtension; | ||||
| import org.springframework.test.web.servlet.MockMvc; | ||||
| import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; | ||||
| import org.springframework.test.web.servlet.result.MockMvcResultMatchers; | ||||
| 
 | ||||
| import java.net.URI; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| 
 | ||||
| @ExtendWith(SpringExtension.class) | ||||
| @AutoConfigureMockMvc | ||||
| @WebMvcTest(UserController.class) | ||||
| @ActiveProfiles("interceptorExample") | ||||
| public class EscapeHtmlInterceptorIntegrationTest { | ||||
|     Logger logger = LoggerFactory.getLogger(EscapeHtmlInterceptorIntegrationTest.class); | ||||
| 
 | ||||
|     @Autowired | ||||
|     private MockMvc mockMvc; | ||||
| 
 | ||||
|     @Test | ||||
|     void givenInterceptor_whenEscapeHtmlInterceptor_thenEscapeHtml() throws Exception { | ||||
|         Map<String, String> requestBody = Map.of( | ||||
|                 "name", "James Cameron", | ||||
|                 "email", "<script>alert()</script>james@gmail.com" | ||||
|         ); | ||||
| 
 | ||||
|         ObjectMapper objectMapper = new ObjectMapper(); | ||||
|         mockMvc.perform(MockMvcRequestBuilders.post(URI.create("/save")) | ||||
|           .contentType(MediaType.APPLICATION_JSON) | ||||
|           .content(objectMapper.writeValueAsString(requestBody))) | ||||
|           .andExpect(MockMvcResultMatchers.status().is4xxClientError()); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user