View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.httpclient;
16  
17  import java.net.MalformedURLException;
18  import java.net.URL;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.List;
22  import java.util.Set;
23  
24  import org.apache.http.cookie.ClientCookie;
25  import org.apache.http.cookie.Cookie;
26  import org.apache.http.cookie.CookieOrigin;
27  import org.apache.http.cookie.CookieSpec;
28  import org.apache.http.cookie.MalformedCookieException;
29  import org.apache.http.impl.cookie.BasicClientCookie;
30  import org.apache.http.message.BufferedHeader;
31  import org.apache.http.util.CharArrayBuffer;
32  import org.htmlunit.BrowserVersion;
33  import org.htmlunit.util.UrlUtils;
34  
35  /**
36   * Helper methods to convert from/to HttpClient.
37   *
38   * @author Ronald Brill
39   */
40  public final class HttpClientConverter {
41  
42      private HttpClientConverter() {
43          // util class
44      }
45  
46      /**
47       * Helper that builds a CookieOrigin.
48       * @param url the url to be used
49       * @return the new CookieOrigin
50       */
51      public static CookieOrigin buildCookieOrigin(final URL url) {
52          final URL normalizedUrl = replaceForCookieIfNecessary(url);
53  
54          int port = normalizedUrl.getPort();
55          if (port == -1) {
56              port = normalizedUrl.getDefaultPort();
57          }
58  
59          return new CookieOrigin(
60                  normalizedUrl.getHost(),
61                  port,
62                  normalizedUrl.getPath(),
63                  "https".equals(normalizedUrl.getProtocol()));
64      }
65  
66      /**
67       * {@link CookieOrigin} doesn't like empty hosts and negative ports,
68       * but these things happen if we're dealing with a local file.
69       * This method allows us to work around this limitation in HttpClient by feeding it a bogus host and port.
70       *
71       * @param url the URL to replace if necessary
72       * @return the replacement URL, or the original URL if no replacement was necessary
73       */
74      public static URL replaceForCookieIfNecessary(URL url) {
75          final String protocol = url.getProtocol();
76          final boolean file = "file".equals(protocol);
77          if (file) {
78              try {
79                  url = UrlUtils.getUrlWithNewHostAndPort(url,
80                          HtmlUnitBrowserCompatCookieSpec.LOCAL_FILESYSTEM_DOMAIN, 0);
81              }
82              catch (final MalformedURLException e) {
83                  throw new RuntimeException(e);
84              }
85          }
86          return url;
87      }
88  
89      /**
90       * @param cookieString the string to parse
91       * @param pageUrl the page url as root
92       * @param browserVersion the {@link BrowserVersion}
93       * @return a list of {@link org.htmlunit.http.Cookie}'s
94       * @throws MalformedCookieException in case the cookie does not conform to the spec
95       */
96      public static List<org.htmlunit.http.Cookie> parseCookie(final String cookieString, final URL pageUrl,
97                                                               final BrowserVersion browserVersion)
98              throws MalformedCookieException {
99          final CharArrayBuffer buffer = new CharArrayBuffer(cookieString.length() + 22);
100         buffer.append("Set-Cookie: ");
101         buffer.append(cookieString);
102 
103         final CookieSpec cookieSpec = new HtmlUnitBrowserCompatCookieSpec(browserVersion);
104         final List<Cookie> cookies = cookieSpec.parse(new BufferedHeader(buffer), buildCookieOrigin(pageUrl));
105 
106         final List<org.htmlunit.http.Cookie> htmlUnitCookies = new ArrayList<>(cookies.size());
107         for (final Cookie cookie : cookies) {
108             final org.htmlunit.http.Cookie htmlUnitCookie = new HttpClientCookie((ClientCookie) cookie);
109             htmlUnitCookies.add(htmlUnitCookie);
110         }
111         return htmlUnitCookies;
112     }
113 
114     /**
115      * Converts the specified collection of cookies into a collection of HttpClient cookies.
116      * @param cookies the cookies to be converted
117      * @return the specified cookies, as HttpClient cookies
118      */
119     public static List<Cookie> toHttpClient(final Collection<org.htmlunit.http.Cookie> cookies) {
120         final ArrayList<Cookie> array = new ArrayList<>(cookies.size());
121         for (final org.htmlunit.http.Cookie cookie : cookies) {
122             array.add(toHttpClient(cookie));
123         }
124         return array;
125     }
126 
127     /**
128      * Adds all matching cookies to the provided set.
129      * @param cookies the cookies to select from
130      * @param normalizedUrl the url to match against
131      * @param browserVersion the {@link BrowserVersion}
132      * @param matches the set to add
133      */
134     public static void addMatching(final Set<org.htmlunit.http.Cookie> cookies,
135             final URL normalizedUrl, final BrowserVersion browserVersion,
136             final Set<org.htmlunit.http.Cookie> matches) {
137         if (!cookies.isEmpty()) {
138             final CookieOrigin cookieOrigin = HttpClientConverter.buildCookieOrigin(normalizedUrl);
139             final CookieSpec cookieSpec = new HtmlUnitBrowserCompatCookieSpec(browserVersion);
140             for (final org.htmlunit.http.Cookie cookie : cookies) {
141                 if (cookieSpec.match(toHttpClient(cookie), cookieOrigin)) {
142                     matches.add(cookie);
143                 }
144             }
145         }
146     }
147 
148     private static ClientCookie toHttpClient(final org.htmlunit.http.Cookie cookie) {
149         if (cookie instanceof HttpClientCookie) {
150             return ((HttpClientCookie) cookie).getHttpClientCookie();
151         }
152 
153         final BasicClientCookie httpClientCookie = new BasicClientCookie(cookie.getName(),
154                 cookie.getValue() == null ? "" : cookie.getValue());
155 
156         httpClientCookie.setDomain(cookie.getDomain());
157         // BasicDomainHandler.match(Cookie, CookieOrigin) checks the attribute also (see #333)
158         httpClientCookie.setAttribute(ClientCookie.DOMAIN_ATTR, cookie.getDomain());
159 
160         httpClientCookie.setPath(cookie.getPath());
161         httpClientCookie.setExpiryDate(cookie.getExpires());
162 
163         httpClientCookie.setSecure(cookie.isSecure());
164         if (cookie.isHttpOnly()) {
165             httpClientCookie.setAttribute("httponly", "true");
166         }
167 
168         if (cookie.getSameSite() != null) {
169             httpClientCookie.setAttribute("samesite", cookie.getSameSite());
170         }
171 
172         return httpClientCookie;
173     }
174 }