1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // For unit tests @see TestCookieManager 20 21 package org.apache.jmeter.protocol.http.control; 22 23 import java.io.BufferedReader; 24 import java.io.File; 25 import java.io.FileReader; 26 import java.io.FileWriter; 27 import java.io.IOException; 28 import java.io.PrintWriter; 29 import java.io.Serializable; 30 import java.net.URL; 31 import java.util.ArrayList; 32 import java.util.Date; 33 34 import org.apache.commons.httpclient.cookie.CookiePolicy; 35 import org.apache.commons.httpclient.cookie.CookieSpec; 36 import org.apache.commons.httpclient.cookie.MalformedCookieException; 37 import org.apache.jmeter.config.ConfigTestElement; 38 import org.apache.jmeter.engine.event.LoopIterationEvent; 39 import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; 40 import org.apache.jmeter.testelement.TestListener; 41 import org.apache.jmeter.testelement.property.BooleanProperty; 42 import org.apache.jmeter.testelement.property.CollectionProperty; 43 import org.apache.jmeter.testelement.property.PropertyIterator; 44 import org.apache.jmeter.threads.JMeterContext; 45 import org.apache.jmeter.util.JMeterUtils; 46 import org.apache.jorphan.logging.LoggingManager; 47 import org.apache.jorphan.util.JOrphanUtils; 48 import org.apache.log.Logger; 49 50 /** 51 * This class provides an interface to the netscape cookies file to pass cookies 52 * along with a request. 53 * 54 * Now uses Commons HttpClient parsing and matching code (since 2.1.2) 55 * 56 */ 57 public class CookieManager extends ConfigTestElement implements TestListener, Serializable { 58 private static final long serialVersionUID = 233L; 59 60 private static final Logger log = LoggingManager.getLoggerForClass(); 61 62 //++ JMX tag values 63 private static final String CLEAR = "CookieManager.clearEachIteration";// $NON-NLS-1$ 64 65 private static final String COOKIES = "CookieManager.cookies";// $NON-NLS-1$ 66 67 private static final String POLICY = "CookieManager.policy"; //$NON-NLS-1$ 68 //-- JMX tag values 69 70 private static final String TAB = "\t"; //$NON-NLS-1$ 71 72 // See bug 33796 73 private static final boolean DELETE_NULL_COOKIES = 74 JMeterUtils.getPropDefault("CookieManager.delete_null_cookies", true);// $NON-NLS-1$ 75 76 // See bug 28715 77 private static final boolean ALLOW_VARIABLE_COOKIES 78 = JMeterUtils.getPropDefault("CookieManager.allow_variable_cookies", true);// $NON-NLS-1$ 79 80 private static final String COOKIE_NAME_PREFIX = 81 JMeterUtils.getPropDefault("CookieManager.name.prefix", "COOKIE_").trim();// $NON-NLS-1$ $NON-NLS-2$ 82 83 private static final boolean SAVE_COOKIES = 84 JMeterUtils.getPropDefault("CookieManager.save.cookies", false);// $NON-NLS-1$ 85 86 private static final boolean CHECK_COOKIES = 87 JMeterUtils.getPropDefault("CookieManager.check.cookies", true);// $NON-NLS-1$ 88 89 private transient CookieSpec cookieSpec; 90 91 private transient CollectionProperty initialCookies; 92 93 public static final String DEFAULT_POLICY = CookiePolicy.BROWSER_COMPATIBILITY; 94 95 public CookieManager() { 96 clearCookies(); // Ensure that there is always a collection available 97 } 98 99 // ensure that the initial cookies are copied to the per-thread instances 100 public Object clone(){ 101 CookieManager clone = (CookieManager) super.clone(); 102 clone.initialCookies = initialCookies; 103 clone.cookieSpec = cookieSpec; 104 return clone; 105 } 106 107 public String getPolicy() { 108 return getPropertyAsString(POLICY, DEFAULT_POLICY); 109 } 110 111 public void setCookiePolicy(String policy){ 112 setProperty(POLICY, policy, DEFAULT_POLICY); 113 } 114 115 public CollectionProperty getCookies() { 116 return (CollectionProperty) getProperty(COOKIES); 117 } 118 119 public int getCookieCount() {// Used by GUI 120 return getCookies().size(); 121 } 122 123 public boolean getClearEachIteration() { 124 return getPropertyAsBoolean(CLEAR); 125 } 126 127 public void setClearEachIteration(boolean clear) { 128 setProperty(new BooleanProperty(CLEAR, clear)); 129 } 130 131 /** 132 * Save the static cookie data to a file. 133 * Cookies are only taken from the GUI - runtime cookies are not included. 134 */ 135 public void save(String authFile) throws IOException { 136 File file = new File(authFile); 137 if (!file.isAbsolute()) { 138 file = new File(System.getProperty("user.dir") // $NON-NLS-1$ 139 + File.separator + authFile); 140 } 141 PrintWriter writer = new PrintWriter(new FileWriter(file)); 142 writer.println("# JMeter generated Cookie file");// $NON-NLS-1$ 143 PropertyIterator cookies = getCookies().iterator(); 144 long now = System.currentTimeMillis(); 145 while (cookies.hasNext()) { 146 Cookie cook = (Cookie) cookies.next().getObjectValue(); 147 final long expiresMillis = cook.getExpiresMillis(); 148 if (expiresMillis == 0 || expiresMillis > now) { // only save unexpired cookies 149 writer.println(cookieToString(cook)); 150 } 151 } 152 writer.flush(); 153 writer.close(); 154 } 155 156 /** 157 * Add cookie data from a file. 158 */ 159 public void addFile(String cookieFile) throws IOException { 160 File file = new File(cookieFile); 161 if (!file.isAbsolute()) { 162 file = new File(System.getProperty("user.dir") // $NON-NLS-1$ 163 + File.separator + cookieFile); 164 } 165 BufferedReader reader = null; 166 if (file.canRead()) { 167 reader = new BufferedReader(new FileReader(file)); 168 } else { 169 throw new IOException("The file you specified cannot be read."); 170 } 171 172 // N.B. this must agree with the save() and cookieToString() methods 173 String line; 174 try { 175 final CollectionProperty cookies = getCookies(); 176 while ((line = reader.readLine()) != null) { 177 try { 178 if (line.startsWith("#") || line.trim().length() == 0) {//$NON-NLS-1$ 179 continue; 180 } 181 String[] st = JOrphanUtils.split(line, TAB, false); 182 183 final int _domain = 0; 184 //final int _ignored = 1; 185 final int _path = 2; 186 final int _secure = 3; 187 final int _expires = 4; 188 final int _name = 5; 189 final int _value = 6; 190 final int _fields = 7; 191 if (st.length!=_fields) { 192 throw new IOException("Expected "+_fields+" fields, found "+st.length+" in "+line); 193 } 194 195 if (st[_path].length()==0) { 196 st[_path] = "/"; //$NON-NLS-1$ 197 } 198 boolean secure = Boolean.valueOf(st[_secure]).booleanValue(); 199 long expires = new Long(st[_expires]).longValue(); 200 if (expires==Long.MAX_VALUE) { 201 expires=0; 202 } 203 //long max was used to represent a non-expiring cookie, but that caused problems 204 Cookie cookie = new Cookie(st[_name], st[_value], st[_domain], st[_path], secure, expires); 205 cookies.addItem(cookie); 206 } catch (NumberFormatException e) { 207 throw new IOException("Error parsing cookie line\n\t'" + line + "'\n\t" + e); 208 } 209 } 210 } finally { 211 reader.close(); 212 } 213 } 214 215 private String cookieToString(Cookie c){ 216 StringBuffer sb=new StringBuffer(80); 217 sb.append(c.getDomain()); 218 //flag - if all machines within a given domain can access the variable. 219 //(from http://www.cookiecentral.com/faq/ 3.5) 220 sb.append(TAB).append("TRUE"); 221 sb.append(TAB).append(c.getPath()); 222 sb.append(TAB).append(JOrphanUtils.booleanToSTRING(c.getSecure())); 223 sb.append(TAB).append(c.getExpires()); 224 sb.append(TAB).append(c.getName()); 225 sb.append(TAB).append(c.getValue()); 226 return sb.toString(); 227 } 228 229 public void recoverRunningVersion() { 230 // do nothing, the cookie manager has to accept changes. 231 } 232 233 public void setRunningVersion(boolean running) { 234 // do nothing, the cookie manager has to accept changes. 235 } 236 237 /** 238 * Add a cookie. 239 */ 240 public void add(Cookie c) { 241 String cv = c.getValue(); 242 String cn = c.getName(); 243 removeMatchingCookies(c); // Can't have two matching cookies 244 245 if (DELETE_NULL_COOKIES && (null == cv || cv.length()==0)) { 246 if (log.isDebugEnabled()) { 247 log.debug("Dropping cookie with null value " + c.toString()); 248 } 249 } else { 250 if (log.isDebugEnabled()) { 251 log.debug("Add cookie to store " + c.toString()); 252 } 253 getCookies().addItem(c); 254 if (SAVE_COOKIES) { 255 JMeterContext context = getThreadContext(); 256 if (context.isSamplingStarted()) { 257 context.getVariables().put(COOKIE_NAME_PREFIX+cn, cv); 258 } 259 } 260 } 261 } 262 263 public void clear(){ 264 super.clear(); 265 clearCookies(); // ensure data is set up OK initially 266 } 267 268 /* 269 * Remove all the cookies. 270 */ 271 private void clearCookies() { 272 log.debug("Clear all cookies from store"); 273 setProperty(new CollectionProperty(COOKIES, new ArrayList())); 274 } 275 276 /** 277 * Remove a cookie. 278 */ 279 public void remove(int index) {// TODO not used by GUI 280 getCookies().remove(index); 281 } 282 283 /** 284 * Return the cookie at index i. 285 */ 286 public Cookie get(int i) {// Only used by GUI 287 return (Cookie) getCookies().get(i).getObjectValue(); 288 } 289 290 /* 291 * Create an HttpClient cookie from a JMeter cookie 292 */ 293 private org.apache.commons.httpclient.Cookie makeCookie(Cookie jmc){ 294 long exp = jmc.getExpiresMillis(); 295 org.apache.commons.httpclient.Cookie ret= 296 new org.apache.commons.httpclient.Cookie( 297 jmc.getDomain(), 298 jmc.getName(), 299 jmc.getValue(), 300 jmc.getPath(), 301 exp > 0 ? new Date(exp) : null, // use null for no expiry 302 jmc.getSecure() 303 ); 304 ret.setPathAttributeSpecified(jmc.isPathSpecified()); 305 ret.setDomainAttributeSpecified(jmc.isDomainSpecified()); 306 ret.setVersion(jmc.getVersion()); 307 return ret; 308 } 309 310 /** 311 * Get array of valid HttpClient cookies for the URL 312 * 313 * @param url the target URL 314 * @return array of HttpClient cookies 315 * 316 */ 317 public org.apache.commons.httpclient.Cookie[] getCookiesForUrl(URL url){ 318 CollectionProperty jar=getCookies(); 319 org.apache.commons.httpclient.Cookie cookies[]= 320 new org.apache.commons.httpclient.Cookie[jar.size()]; 321 int i=0; 322 for (PropertyIterator iter = getCookies().iterator(); iter.hasNext();) { 323 Cookie jmcookie = (Cookie) iter.next().getObjectValue(); 324 // Set to running version, to allow function evaluation for the cookie values (bug 28715) 325 if (ALLOW_VARIABLE_COOKIES) { 326 jmcookie.setRunningVersion(true); 327 } 328 cookies[i++] = makeCookie(jmcookie); 329 if (ALLOW_VARIABLE_COOKIES) { 330 jmcookie.setRunningVersion(false); 331 } 332 } 333 String host = url.getHost(); 334 String protocol = url.getProtocol(); 335 int port= HTTPSamplerBase.getDefaultPort(protocol,url.getPort()); 336 String path = url.getPath(); 337 boolean secure = HTTPSamplerBase.isSecure(protocol); 338 return cookieSpec.match(host, port, path, secure, cookies); 339 } 340 341 /** 342 * Find cookies applicable to the given URL and build the Cookie header from 343 * them. 344 * 345 * @param url 346 * URL of the request to which the returned header will be added. 347 * @return the value string for the cookie header (goes after "Cookie: "). 348 */ 349 public String getCookieHeaderForURL(URL url) { 350 org.apache.commons.httpclient.Cookie[] c = getCookiesForUrl(url); 351 int count = c.length; 352 boolean debugEnabled = log.isDebugEnabled(); 353 if (debugEnabled){ 354 log.debug("Found "+count+" cookies for "+url.toExternalForm()); 355 } 356 if (count <=0){ 357 return null; 358 } 359 String hdr=cookieSpec.formatCookieHeader(c).getValue(); 360 if (debugEnabled){ 361 log.debug("Cookie: "+hdr); 362 } 363 return hdr; 364 } 365 366 367 public void addCookieFromHeader(String cookieHeader, URL url){ 368 boolean debugEnabled = log.isDebugEnabled(); 369 if (debugEnabled) { 370 log.debug("Received Cookie: " + cookieHeader + " From: " + url.toExternalForm()); 371 } 372 String protocol = url.getProtocol(); 373 String host = url.getHost(); 374 int port= HTTPSamplerBase.getDefaultPort(protocol,url.getPort()); 375 String path = url.getPath(); 376 boolean isSecure=HTTPSamplerBase.isSecure(protocol); 377 org.apache.commons.httpclient.Cookie[] cookies= null; 378 try { 379 cookies = cookieSpec.parse(host, port, path, isSecure, cookieHeader); 380 } catch (MalformedCookieException e) { 381 log.warn(cookieHeader+e.getLocalizedMessage()); 382 } catch (IllegalArgumentException e) { 383 log.warn(cookieHeader+e.getLocalizedMessage()); 384 } 385 if (cookies == null) { 386 return; 387 } 388 for(int i=0;i<cookies.length;i++){ 389 org.apache.commons.httpclient.Cookie cookie = cookies[i]; 390 try { 391 if (CHECK_COOKIES) { 392 cookieSpec.validate(host, port, path, isSecure, cookie); 393 } 394 Date expiryDate = cookie.getExpiryDate(); 395 long exp = 0; 396 if (expiryDate!= null) { 397 exp=expiryDate.getTime(); 398 } 399 Cookie newCookie = new Cookie( 400 cookie.getName(), 401 cookie.getValue(), 402 cookie.getDomain(), 403 cookie.getPath(), 404 cookie.getSecure(), 405 exp / 1000, 406 cookie.isPathAttributeSpecified(), 407 cookie.isDomainAttributeSpecified() 408 ); 409 410 // Store session cookies as well as unexpired ones 411 if (exp == 0 || exp >= System.currentTimeMillis()) { 412 newCookie.setVersion(cookie.getVersion()); 413 add(newCookie); // Has its own debug log; removes matching cookies 414 } else { 415 removeMatchingCookies(newCookie); 416 if (debugEnabled){ 417 log.debug("Dropping expired Cookie: "+newCookie.toString()); 418 } 419 } 420 } catch (MalformedCookieException e) { // This means the cookie was wrong for the URL 421 log.debug("Not storing invalid cookie: <"+cookieHeader+"> for URL "+url+" ("+e.getLocalizedMessage()+")"); 422 } catch (IllegalArgumentException e) { 423 log.warn(cookieHeader+e.getLocalizedMessage()); 424 } 425 } 426 427 } 428 /** 429 * Check if cookies match, i.e. name, path and domain are equal. 430 * <br/> 431 * TODO - should we compare secure too? 432 * @param a 433 * @param b 434 * @return true if cookies match 435 */ 436 private boolean match(Cookie a, Cookie b){ 437 return 438 a.getName().equals(b.getName()) 439 && 440 a.getPath().equals(b.getPath()) 441 && 442 a.getDomain().equals(b.getDomain()); 443 } 444 445 private void removeMatchingCookies(Cookie newCookie){ 446 // Scan for any matching cookies 447 PropertyIterator iter = getCookies().iterator(); 448 while (iter.hasNext()) { 449 Cookie cookie = (Cookie) iter.next().getObjectValue(); 450 if (cookie == null) {// TODO is this possible? 451 continue; 452 } 453 if (match(cookie,newCookie)) { 454 if (log.isDebugEnabled()) { 455 log.debug("New Cookie = " + newCookie.toString() 456 + " removing matching Cookie " + cookie.toString()); 457 } 458 iter.remove(); 459 } 460 } 461 } 462 463 public void testStarted() { 464 initialCookies = getCookies(); 465 cookieSpec = CookiePolicy.getCookieSpec(getPolicy()); 466 if (log.isDebugEnabled()){ 467 log.debug("Policy: "+getPolicy()+" Clear: "+getClearEachIteration()); 468 } 469 } 470 471 public void testEnded() { 472 } 473 474 public void testStarted(String host) { 475 testStarted(); 476 } 477 478 public void testEnded(String host) { 479 } 480 481 public void testIterationStart(LoopIterationEvent event) { 482 if (getClearEachIteration()) { 483 log.debug("Initialise cookies from pre-defined list"); 484 // No need to call clear 485 setProperty((CollectionProperty)initialCookies.clone()); 486 } 487 } 488 }