001/* 002 * Copyright 2022-2025 Revetware LLC. 003 * 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 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 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 */ 016 017package com.soklet.core; 018 019import javax.annotation.Nonnull; 020import javax.annotation.Nullable; 021import javax.annotation.concurrent.ThreadSafe; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Optional; 025import java.util.Set; 026 027import static com.soklet.core.Utilities.trimAggressivelyToNull; 028import static java.util.Objects.requireNonNull; 029 030/** 031 * Encapsulates <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">non-preflight CORS</a> HTTP request data. 032 * <p> 033 * Data for <a href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request">preflight</a> requests is represented by {@link CorsPreflight}. 034 * <p> 035 * See <a href="https://www.soklet.com/docs/cors">https://www.soklet.com/docs/cors</a> for detailed documentation. 036 * 037 * @author <a href="https://www.revetkn.com">Mark Allen</a> 038 */ 039@ThreadSafe 040public class Cors { 041 @Nullable 042 private final HttpMethod httpMethod; 043 @Nonnull 044 private final String origin; 045 046 /** 047 * Constructs a CORS <strong>non-preflight</strong> request representation for the given HTTP request data. 048 * 049 * @param httpMethod the request's HTTP method 050 * @param origin HTTP <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin">{@code Origin}</a> request header value 051 */ 052 public Cors(@Nonnull HttpMethod httpMethod, 053 @Nonnull String origin) { 054 requireNonNull(httpMethod); 055 requireNonNull(origin); 056 057 this.httpMethod = httpMethod; 058 this.origin = origin; 059 } 060 061 /** 062 * Extracts a CORS <strong>non-preflight</strong> request representation from the given HTTP request data. 063 * 064 * @param httpMethod the request's HTTP method 065 * @param headers the request headers 066 * @return the CORS non-preflight data for this request, or {@link Optional#empty()} if insufficent data is present 067 */ 068 @Nonnull 069 public static Optional<Cors> fromHeaders(@Nonnull HttpMethod httpMethod, 070 @Nonnull Map<String, Set<String>> headers) { 071 requireNonNull(httpMethod); 072 requireNonNull(headers); 073 074 Set<String> originHeaderValues = headers.get("Origin"); 075 076 if (originHeaderValues == null || originHeaderValues.size() == 0) 077 return Optional.empty(); 078 079 String originHeaderValue = trimAggressivelyToNull(originHeaderValues.stream().findFirst().orElse(null)); 080 081 if (originHeaderValue == null) 082 return Optional.empty(); 083 084 return Optional.of(new Cors(httpMethod, originHeaderValue)); 085 } 086 087 @Override 088 @Nonnull 089 public String toString() { 090 return String.format("%s{httpMethod=%s, origin=%s}", getClass().getSimpleName(), getHttpMethod().name(), getOrigin()); 091 } 092 093 @Override 094 public boolean equals(@Nullable Object object) { 095 if (this == object) 096 return true; 097 098 if (!(object instanceof Cors cors)) 099 return false; 100 101 return Objects.equals(getHttpMethod(), cors.getHttpMethod()) 102 && Objects.equals(getOrigin(), cors.getOrigin()); 103 } 104 105 @Override 106 public int hashCode() { 107 return Objects.hash(getHttpMethod(), getOrigin()); 108 } 109 110 /** 111 * The HTTP method for this request. 112 * 113 * @return the HTTP method 114 */ 115 @Nullable 116 public HttpMethod getHttpMethod() { 117 return this.httpMethod; 118 } 119 120 /** 121 * The HTTP <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin">{@code Origin}</a> header value for this request. 122 * 123 * @return the header value 124 */ 125 @Nonnull 126 public String getOrigin() { 127 return this.origin; 128 } 129}