001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: // For unit tests @see TestCookieManager
020: package org.apache.jmeter.protocol.http.control;
021:
022: import java.io.BufferedReader;
023: import java.io.File;
024: import java.io.FileReader;
025: import java.io.FileWriter;
026: import java.io.IOException;
027: import java.io.PrintWriter;
028: import java.io.Serializable;
029: import java.net.URL;
030: import java.util.ArrayList;
031: import java.util.Date;
032:
033: import org.apache.commons.httpclient.cookie.CookiePolicy;
034: import org.apache.commons.httpclient.cookie.CookieSpec;
035: import org.apache.commons.httpclient.cookie.MalformedCookieException;
036: import org.apache.jmeter.config.ConfigTestElement;
037: import org.apache.jmeter.engine.event.LoopIterationEvent;
038: import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
039: import org.apache.jmeter.testelement.TestListener;
040: import org.apache.jmeter.testelement.property.BooleanProperty;
041: import org.apache.jmeter.testelement.property.CollectionProperty;
042: import org.apache.jmeter.testelement.property.PropertyIterator;
043: import org.apache.jmeter.threads.JMeterContext;
044: import org.apache.jmeter.util.JMeterUtils;
045: import org.apache.jorphan.logging.LoggingManager;
046: import org.apache.jorphan.util.JOrphanUtils;
047: import org.apache.log.Logger;
048:
049: /**
050: * This class provides an interface to the netscape cookies file to pass cookies
051: * along with a request.
052: *
053: * Now uses Commons HttpClient parsing and matching code (since 2.1.2)
054: *
055: */
056: public class CookieManager extends ConfigTestElement implements
057: TestListener, Serializable {
058: private static final Logger log = LoggingManager
059: .getLoggerForClass();
060:
061: public static final String CLEAR = "CookieManager.clearEachIteration";// $NON-NLS-1$
062:
063: public static final String COOKIES = "CookieManager.cookies";// $NON-NLS-1$
064:
065: public static final String POLICY = "CookieManager.policy"; //$NON-NLS-1$
066:
067: private static final String TAB = "\t"; //$NON-NLS-1$
068:
069: // See bug 33796
070: private static final boolean DELETE_NULL_COOKIES = JMeterUtils
071: .getPropDefault("CookieManager.delete_null_cookies", true);// $NON-NLS-1$
072:
073: // See bug 28715
074: private static final boolean ALLOW_VARIABLE_COOKIES = JMeterUtils
075: .getPropDefault("CookieManager.allow_variable_cookies",
076: true);// $NON-NLS-1$
077:
078: private transient CookieSpec cookieSpec;
079:
080: private transient CollectionProperty initialCookies;
081:
082: public static final String DEFAULT_POLICY = CookiePolicy.BROWSER_COMPATIBILITY;
083:
084: public CookieManager() {
085: setProperty(new CollectionProperty(COOKIES, new ArrayList()));
086: setProperty(new BooleanProperty(CLEAR, false));
087: setCookiePolicy(DEFAULT_POLICY);
088: }
089:
090: // ensure that the initial cookies are copied to the per-thread instances
091: public Object clone() {
092: CookieManager clone = (CookieManager) super .clone();
093: clone.initialCookies = initialCookies;
094: return clone;
095: }
096:
097: public String getPolicy() {
098: return getPropertyAsString(POLICY, DEFAULT_POLICY);
099: }
100:
101: public void setCookiePolicy(String policy) {
102: cookieSpec = CookiePolicy.getCookieSpec(policy);
103: if (!DEFAULT_POLICY.equals(policy)) {// Don't clutter the JMX file
104: setProperty(POLICY, policy);
105: }
106: }
107:
108: public CollectionProperty getCookies() {
109: return (CollectionProperty) getProperty(COOKIES);
110: }
111:
112: public int getCookieCount() {// Used by GUI
113: return getCookies().size();
114: }
115:
116: public boolean getClearEachIteration() {
117: return getPropertyAsBoolean(CLEAR);
118: }
119:
120: public void setClearEachIteration(boolean clear) {
121: setProperty(new BooleanProperty(CLEAR, clear));
122: }
123:
124: /**
125: * Save the static cookie data to a file.
126: * Cookies are only taken from the GUI - runtime cookies are not included.
127: */
128: public void save(String authFile) throws IOException {
129: File file = new File(authFile);
130: if (!file.isAbsolute())
131: file = new File(System.getProperty("user.dir") // $NON-NLS-1$
132: + File.separator + authFile);
133: PrintWriter writer = new PrintWriter(new FileWriter(file));
134: writer.println("# JMeter generated Cookie file");// $NON-NLS-1$
135: PropertyIterator cookies = getCookies().iterator();
136: long now = System.currentTimeMillis();
137: while (cookies.hasNext()) {
138: Cookie cook = (Cookie) cookies.next().getObjectValue();
139: final long expiresMillis = cook.getExpiresMillis();
140: if (expiresMillis == 0 || expiresMillis > now) { // only save unexpired cookies
141: writer.println(cookieToString(cook));
142: }
143: }
144: writer.flush();
145: writer.close();
146: }
147:
148: /**
149: * Add cookie data from a file.
150: */
151: public void addFile(String cookieFile) throws IOException {
152: File file = new File(cookieFile);
153: if (!file.isAbsolute())
154: file = new File(System.getProperty("user.dir") // $NON-NLS-1$
155: + File.separator + cookieFile);
156: BufferedReader reader = null;
157: if (file.canRead()) {
158: reader = new BufferedReader(new FileReader(file));
159: } else {
160: throw new IOException(
161: "The file you specified cannot be read.");
162: }
163:
164: // N.B. this must agree with the save() and cookieToString() methods
165: String line;
166: try {
167: final CollectionProperty cookies = getCookies();
168: while ((line = reader.readLine()) != null) {
169: try {
170: if (line.startsWith("#") || line.trim().length() == 0)//$NON-NLS-1$
171: continue;
172: String[] st = JOrphanUtils.split(line, TAB, false);
173:
174: final int _domain = 0;
175: //final int _ignored = 1;
176: final int _path = 2;
177: final int _secure = 3;
178: final int _expires = 4;
179: final int _name = 5;
180: final int _value = 6;
181: final int _fields = 7;
182: if (st.length != _fields) {
183: throw new IOException("Expected " + _fields
184: + " fields, found " + st.length
185: + " in " + line);
186: }
187:
188: if (st[_path].length() == 0)
189: st[_path] = "/"; //$NON-NLS-1$
190: boolean secure = Boolean.valueOf(st[_secure])
191: .booleanValue();
192: long expires = new Long(st[_expires]).longValue();
193: if (expires == Long.MAX_VALUE)
194: expires = 0;
195: //long max was used to represent a non-expiring cookie, but that caused problems
196: Cookie cookie = new Cookie(st[_name], st[_value],
197: st[_domain], st[_path], secure, expires);
198: cookies.addItem(cookie);
199: } catch (NumberFormatException e) {
200: throw new IOException(
201: "Error parsing cookie line\n\t'" + line
202: + "'\n\t" + e);
203: }
204: }
205: } finally {
206: reader.close();
207: }
208: }
209:
210: private String cookieToString(Cookie c) {
211: StringBuffer sb = new StringBuffer(80);
212: sb.append(c.getDomain());
213: //flag - if all machines within a given domain can access the variable.
214: //(from http://www.cookiecentral.com/faq/ 3.5)
215: sb.append(TAB).append("TRUE");
216: sb.append(TAB).append(c.getPath());
217: sb.append(TAB).append(
218: JOrphanUtils.booleanToSTRING(c.getSecure()));
219: sb.append(TAB).append(c.getExpires());
220: sb.append(TAB).append(c.getName());
221: sb.append(TAB).append(c.getValue());
222: return sb.toString();
223: }
224:
225: public void recoverRunningVersion() {
226: // do nothing, the cookie manager has to accept changes.
227: }
228:
229: public void setRunningVersion(boolean running) {
230: // do nothing, the cookie manager has to accept changes.
231: }
232:
233: /**
234: * Add a cookie.
235: */
236: public void add(Cookie c) {
237: String cv = c.getValue();
238: String cn = c.getName();
239: removeMatchingCookies(c); // Can't have two matching cookies
240:
241: if (DELETE_NULL_COOKIES && (null == cv || cv.length() == 0)) {
242: if (log.isDebugEnabled()) {
243: log.debug("Dropping cookie with null value "
244: + c.toString());
245: }
246: } else {
247: if (log.isDebugEnabled()) {
248: log.debug("Add cookie to store " + c.toString());
249: }
250: getCookies().addItem(c);
251: // Store cookie as a thread variable.
252: // TODO - should we add a prefix to these variables?
253: // TODO - should storing cookie values be optional?
254: JMeterContext context = getThreadContext();
255: if (context.isSamplingStarted()) {
256: context.getVariables().put(cn, cv);
257: }
258: }
259: }
260:
261: public void clear() {
262: super .clear();
263: clearCookies(); // ensure data is set up OK initially
264: }
265:
266: /*
267: * Remove all the cookies.
268: */
269: private void clearCookies() {
270: log.debug("Clear all cookies from store");
271: setProperty(new CollectionProperty(COOKIES, new ArrayList()));
272: }
273:
274: /**
275: * Remove a cookie.
276: */
277: public void remove(int index) {// TODO not used by GUI
278: getCookies().remove(index);
279: }
280:
281: /**
282: * Return the cookie at index i.
283: */
284: public Cookie get(int i) {// Only used by GUI
285: return (Cookie) getCookies().get(i).getObjectValue();
286: }
287:
288: /*
289: * Create an HttpClient cookie from a JMeter cookie
290: */
291: private org.apache.commons.httpclient.Cookie makeCookie(Cookie jmc) {
292: long exp = jmc.getExpiresMillis();
293: org.apache.commons.httpclient.Cookie ret = new org.apache.commons.httpclient.Cookie(
294: jmc.getDomain(), jmc.getName(), jmc.getValue(), jmc
295: .getPath(), exp > 0 ? new Date(exp) : null, // use null for no expiry
296: jmc.getSecure());
297: ret.setPathAttributeSpecified(jmc.isPathSpecified());
298: ret.setDomainAttributeSpecified(jmc.isDomainSpecified());
299: return ret;
300: }
301:
302: /**
303: * Get array of valid HttpClient cookies for the URL
304: *
305: * @param url the target URL
306: * @return array of HttpClient cookies
307: *
308: */
309: public org.apache.commons.httpclient.Cookie[] getCookiesForUrl(
310: URL url) {
311: CollectionProperty jar = getCookies();
312: org.apache.commons.httpclient.Cookie cookies[] = new org.apache.commons.httpclient.Cookie[jar
313: .size()];
314: int i = 0;
315: for (PropertyIterator iter = getCookies().iterator(); iter
316: .hasNext();) {
317: Cookie jmcookie = (Cookie) iter.next().getObjectValue();
318: // Set to running version, to allow function evaluation for the cookie values (bug 28715)
319: if (ALLOW_VARIABLE_COOKIES)
320: jmcookie.setRunningVersion(true);
321: cookies[i++] = makeCookie(jmcookie);
322: if (ALLOW_VARIABLE_COOKIES)
323: jmcookie.setRunningVersion(false);
324: }
325: String host = url.getHost();
326: String protocol = url.getProtocol();
327: int port = HTTPSamplerBase.getDefaultPort(protocol, url
328: .getPort());
329: String path = url.getPath();
330: boolean secure = HTTPSamplerBase.isSecure(protocol);
331: return cookieSpec.match(host, port, path, secure, cookies);
332: }
333:
334: /**
335: * Find cookies applicable to the given URL and build the Cookie header from
336: * them.
337: *
338: * @param url
339: * URL of the request to which the returned header will be added.
340: * @return the value string for the cookie header (goes after "Cookie: ").
341: */
342: public String getCookieHeaderForURL(URL url) {
343: org.apache.commons.httpclient.Cookie[] c = getCookiesForUrl(url);
344: int count = c.length;
345: boolean debugEnabled = log.isDebugEnabled();
346: if (debugEnabled) {
347: log.debug("Found " + count + " cookies for "
348: + url.toExternalForm());
349: }
350: if (count <= 0) {
351: return null;
352: }
353: String hdr = cookieSpec.formatCookieHeader(c).getValue();
354: if (debugEnabled) {
355: log.debug("Cookie: " + hdr);
356: }
357: return hdr;
358: }
359:
360: public void addCookieFromHeader(String cookieHeader, URL url) {
361: boolean debugEnabled = log.isDebugEnabled();
362: if (debugEnabled) {
363: log.debug("Received Cookie: " + cookieHeader + " From: "
364: + url.toExternalForm());
365: }
366: String protocol = url.getProtocol();
367: String host = url.getHost();
368: int port = HTTPSamplerBase.getDefaultPort(protocol, url
369: .getPort());
370: String path = url.getPath();
371: boolean isSecure = HTTPSamplerBase.isSecure(protocol);
372: org.apache.commons.httpclient.Cookie[] cookies = null;
373: try {
374: cookies = cookieSpec.parse(host, port, path, isSecure,
375: cookieHeader);
376: } catch (MalformedCookieException e) {
377: log.warn(cookieHeader + e.getLocalizedMessage());
378: } catch (IllegalArgumentException e) {
379: log.warn(cookieHeader + e.getLocalizedMessage());
380: }
381: if (cookies == null)
382: return;
383: for (int i = 0; i < cookies.length; i++) {
384: Date expiryDate = cookies[i].getExpiryDate();
385: long exp = 0;
386: if (expiryDate != null) {
387: exp = expiryDate.getTime();
388: }
389: Cookie newCookie = new Cookie(cookies[i].getName(),
390: cookies[i].getValue(), cookies[i].getDomain(),
391: cookies[i].getPath(), cookies[i].getSecure(),
392: exp / 1000, cookies[i].isPathAttributeSpecified(),
393: cookies[i].isDomainAttributeSpecified());
394:
395: // Store session cookies as well as unexpired ones
396: if (exp == 0 || exp >= System.currentTimeMillis()) {
397: add(newCookie); // Has its own debug log; removes matching cookies
398: } else {
399: removeMatchingCookies(newCookie);
400: if (debugEnabled) {
401: log.debug("Dropping expired Cookie: "
402: + newCookie.toString());
403: }
404: }
405: }
406:
407: }
408:
409: private boolean match(Cookie a, Cookie b) {
410: return a.getName().equals(b.getName())
411: && a.getPath().equals(b.getPath())
412: && a.getDomain().equals(b.getDomain());
413: }
414:
415: private void removeMatchingCookies(Cookie newCookie) {
416: // Scan for any matching cookies
417: PropertyIterator iter = getCookies().iterator();
418: while (iter.hasNext()) {
419: Cookie cookie = (Cookie) iter.next().getObjectValue();
420: if (cookie == null)
421: continue;
422: if (match(cookie, newCookie)) {
423: if (log.isDebugEnabled()) {
424: log.debug("New Cookie = " + newCookie.toString()
425: + " removing matching Cookie "
426: + cookie.toString());
427: }
428: iter.remove();
429: }
430: }
431: }
432:
433: public String getClassLabel() {
434: return JMeterUtils.getResString("cookie_manager_title");// $NON-NLS-1$
435: }
436:
437: public void testStarted() {
438: initialCookies = getCookies();
439: }
440:
441: public void testEnded() {
442: }
443:
444: public void testStarted(String host) {
445: testStarted();
446: }
447:
448: public void testEnded(String host) {
449: }
450:
451: public void testIterationStart(LoopIterationEvent event) {
452: if (getClearEachIteration()) {
453: clearCookies();
454: setProperty(initialCookies);
455: }
456: }
457: }
|