source: trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/web/WeblogParser.java @ 410

Last change on this file since 410 was 410, checked in by sherbold, 12 years ago
  • improved detection of invalid sequences in de.ugoe.cs.eventbench.web.WeblogParser?
  • Property svn:mime-type set to text/plain
File size: 12.5 KB
Line 
1package de.ugoe.cs.eventbench.web;
2
3import java.io.FileNotFoundException;
4import java.io.IOException;
5import java.net.URI;
6import java.net.URISyntaxException;
7import java.text.ParseException;
8import java.text.SimpleDateFormat;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17
18import de.ugoe.cs.eventbench.web.data.WebEvent;
19import de.ugoe.cs.util.FileTools;
20import de.ugoe.cs.util.console.Console;
21
22/**
23 * <p>
24 * Provides functionality to parse log files with web request.
25 * </p>
26 *
27 * @author Steffen Herbold
28 * @version 1.0
29 */
30public class WeblogParser {
31
32        /**
33         * <p>
34         * Timeout between two sessions in milliseconds.
35         * </p>
36         */
37        private long timeout;
38
39        /**
40         * <p>
41         * Minimal length of a session. All shorter sessions will be pruned.<br>
42         * Default: 2
43         * </p>
44         */
45        private int minLength = 2;
46
47        /**
48         * <p>
49         * Maximal length of a session. All longer sessions will be pruned.<br>
50         * Default: 100
51         * </p>
52         */
53        private int maxLength = 100;
54
55        /**
56         * <p>
57         * URL of the server that generated the log that is currently parser; null
58         * of URL is not available.<br>
59         * Default: null
60         * </p>
61         */
62        private String url = null;
63
64        /**
65         * <p>
66         * Collection of generated sequences.
67         * </p>
68         */
69        private List<List<WebEvent>> sequences;
70
71        /**
72         * <p>
73         * List that stores the users (identified through their cookie id) to each
74         * sequence.
75         * </p>
76         */
77        private List<String> users;
78
79        /**
80         * <p>
81         * List that stores the frequent users (identified through their cookie id)
82         * to each sequence.
83         * </p>
84         */
85        private List<String> frequentUsers;
86
87        /**
88         * <p>
89         * Sequences for all frequent users.
90         * </p>
91         */
92        private List<Collection<List<WebEvent>>> sequencesFrequentUsers;
93
94        /**
95         * <p>
96         * Threshold that defines how many sessions of a user are require to deem
97         * the user frequent. Note, that only sessions whose lengths is in range if
98         * {@link #minLength} and {@link #maxLength} are counted.
99         * </p>
100         */
101        private int frequentUsersThreshold = -1;
102
103        /**
104         * <p>
105         * Name and path of the robot filter.
106         * </p>
107         */
108        private static final String ROBOTFILTERFILE = "misc/robotfilter.txt";
109
110        /**
111         * <p>
112         * Field that contains a regular expression that matches all robots
113         * contained in {@link #ROBOTFILTERFILE}.
114         * </p>
115         */
116        private String robotRegex = null;
117
118        /**
119         * <p>
120         * Constructor. Creates a new WeblogParser with a default timeout of
121         * 3,600,000 milliseconds (1 hour).
122         * </p>
123         */
124        public WeblogParser() {
125                this(3600000);
126        }
127
128        /**
129         * <p>
130         * Constructor. Creates a new WeblogParser.
131         * </p>
132         *
133         * @param timeout
134         *            session timeout
135         */
136        public WeblogParser(long timeout) {
137                this.timeout = timeout;
138        }
139
140        /**
141         * <p>
142         * Returns the generated event sequences.
143         * </p>
144         *
145         * @return generated event sequences
146         */
147        public Collection<List<WebEvent>> getSequences() {
148                return sequences;
149        }
150
151        /**
152         * <p>
153         * Sets the session timeout.
154         * </p>
155         *
156         * @param timeout
157         *            new session timeout
158         */
159        public void setTimeout(long timeout) {
160                this.timeout = timeout;
161        }
162
163        /**
164         * <p>
165         * Sets the minimal length of a session. All sessions that contain less
166         * events will be pruned.
167         * </p>
168         *
169         * @param minLength
170         *            new minimal length
171         */
172        public void setMinLength(int minLength) {
173                this.minLength = minLength;
174        }
175
176        /**
177         * <p>
178         * Sets the maximal length of a session. All sessions that contain more
179         * events will be pruned.
180         * </p>
181         *
182         * @param maxLength
183         *            new maximal length
184         */
185        public void setMaxLength(int maxLength) {
186                this.maxLength = maxLength;
187        }
188
189        /**
190         * <p>
191         * Sets the URL of the server from which this log was generated. Often
192         * required for replay generation
193         * </p>
194         *
195         * @param url
196         *            URL of the server
197         */
198        public void setUrl(String url) {
199                this.url = url;
200        }
201
202        /**
203         * <p>
204         * Sets the threshold for frequent users.
205         * </p>
206         *
207         * @param threshold
208         *            threshold value; if the value is &lt;1, the sessions of the
209         *            frequent users will not be determined
210         */
211        public void setFrequentUserThreshold(int threshold) {
212                this.frequentUsersThreshold = threshold;
213        }
214
215        /**
216         * <p>
217         * Returns the IDs of all frequent users.
218         * </p>
219         *
220         * @return IDs of the frequent users
221         */
222        public List<String> getFrequentUsers() {
223                return frequentUsers;
224        }
225
226        /**
227         * <p>
228         * Returns the sequences of all frequent users.
229         * </p>
230         * </p>
231         *
232         * @return list of the sequences of all frequent users
233         */
234        public List<Collection<List<WebEvent>>> getFrequentUserSequences() {
235                return sequencesFrequentUsers;
236        }
237
238        /**
239         * <p>
240         * Parses a web log file.
241         * </p>
242         *
243         * @param filename
244         *            name and path of the log file
245         * @throws IOException
246         *             thrown if there is a problem with reading the log file
247         * @throws FileNotFoundException
248         *             thrown if the log file is not found
249         * @throws ParseException
250         *             thrown the date format is invalid
251         */
252        public void parseFile(String filename) throws IOException,
253                        FileNotFoundException, ParseException {
254                String[] lines = FileTools.getLinesFromFile(filename);
255
256                Map<String, List<Integer>> cookieSessionMap = new HashMap<String, List<Integer>>();
257                int lastId = -1;
258
259                SimpleDateFormat dateFormat = new SimpleDateFormat(
260                                "yyyy-MM-dd HH:mm:ss");
261                loadRobotRegex();
262
263                sequences = new ArrayList<List<WebEvent>>();
264                users = new ArrayList<String>();
265
266                int lineCounter = 0;
267                for (String line : lines) {
268                        lineCounter++;
269                        String[] values = line.substring(1, line.length() - 1).split(
270                                        "\" \"");
271
272                        // use cookie as session identifier
273                        int cookieStart = values[0].lastIndexOf('.');
274                        String cookie = values[0].substring(cookieStart + 1);
275                        String dateString = values[1];
276                        long timestamp = dateFormat.parse(dateString).getTime();
277                        String uriString = values[2];
278                        // String ref = values[3]; // referer is not yet used!
279                        String agent;
280                        if (values.length > 4) {
281                                agent = values[4];
282                        } else {
283                                agent = "noagent";
284                        }
285
286                        List<String> postedVars = new ArrayList<String>();
287                        if (values.length == 6) { // post vars found
288                                for (String postVar : values[5].trim().split(" ")) {
289                                        if (!isBrokenVariable(postVar)) {
290                                                postedVars.add(postVar);
291                                        }
292                                }
293                        }
294                        if (!isRobot(agent)) {
295                                try {
296                                        URI uri = new URI(uriString);
297                                        String path = uri.getPath();
298                                        List<String> getVars = extractGetVarsFromUri(uri);
299                                       
300                                        WebEvent event = new WebEvent(url, path, timestamp,
301                                                        postedVars, getVars);
302
303                                        // find session and add event
304                                        List<Integer> sessionIds = cookieSessionMap.get(cookie);
305                                        if (sessionIds == null) {
306                                                sessionIds = new ArrayList<Integer>();
307                                                // start new session
308                                                sessionIds.add(++lastId);
309                                                cookieSessionMap.put(cookie, sessionIds);
310                                                sequences.add(new LinkedList<WebEvent>());
311                                                users.add(cookie);
312                                        }
313                                        Integer lastSessionIndex = sessionIds
314                                                        .get(sessionIds.size() - 1);
315                                        List<WebEvent> lastSession = sequences
316                                                        .get(lastSessionIndex);
317                                        long lastEventTime = timestamp;
318                                        if (!lastSession.isEmpty()) {
319                                                lastEventTime = lastSession.get(lastSession.size() - 1)
320                                                                .getTimestamp();
321                                        }
322                                        if (timestamp - lastEventTime > timeout) {
323                                                sessionIds.add(++lastId);
324                                                List<WebEvent> newSession = new LinkedList<WebEvent>();
325                                                newSession.add(event);
326                                                sequences.add(newSession);
327                                                users.add(cookie);
328                                        } else {
329                                                lastSession.add(event);
330                                        }
331                                } catch (URISyntaxException e) {
332                                        Console.traceln("Ignored line " + lineCounter + ": "
333                                                        + e.getMessage());
334                                }
335                        }
336                }
337                Console.traceln("" + sequences.size() + " user sequences found");
338                pruneSequences();
339                Console.traceln("" + sequences.size()
340                                + " remaining after pruning of sequences shorter than "
341                                + minLength);
342                Set<String> uniqueUsers = new HashSet<String>(users);
343                Console.traceln("" + uniqueUsers.size() + " unique users");
344                if (frequentUsersThreshold > 0) {
345                        generateFrequentUserSequences(uniqueUsers);
346                }
347        }
348
349        /**
350         * <p>
351         * Generates the frequent user sequences, according to the threshold
352         * {@link #frequentUsersThreshold}.
353         * </p>
354         *
355         * @param uniqueUsers
356         *            set with all user IDs
357         */
358        private void generateFrequentUserSequences(Set<String> uniqueUsers) {
359                frequentUsers = new ArrayList<String>();
360                sequencesFrequentUsers = new ArrayList<Collection<List<WebEvent>>>();
361                for (String user : uniqueUsers) {
362                        List<String> tmp = new ArrayList<String>();
363                        tmp.add(user);
364                        List<String> usersCopy = new LinkedList<String>(users);
365                        usersCopy.retainAll(tmp);
366                        int size = usersCopy.size();
367                        if (size >= frequentUsersThreshold) {
368                                frequentUsers.add(user);
369                                Collection<List<WebEvent>> sequencesUser = new ArrayList<List<WebEvent>>();
370                                for (int i = 0; i < sequences.size(); i++) {
371                                        if (users.get(i).equals(user)) {
372                                                sequencesUser.add(sequences.get(i));
373                                        }
374                                }
375                                sequencesFrequentUsers.add(sequencesUser);
376
377                        }
378                }
379                Console.traceln("" + frequentUsers.size() + " users with more than "
380                                + frequentUsersThreshold + " sequences");
381        }
382
383        /**
384         * <p>
385         * Prunes sequences shorter than {@link #minLength} and longer than
386         * {@link #maxLength}.
387         * </p>
388         */
389        private void pruneSequences() {
390                int i = 0;
391                while (i < sequences.size()) {
392                        if ((sequences.get(i).size() < minLength)
393                                        || sequences.get(i).size() > maxLength) {
394                                sequences.remove(i);
395                                users.remove(i);
396                        } else {
397                                i++;
398                        }
399                }
400
401        }
402
403        /**
404         * <p>
405         * Reads {@link #ROBOTFILTERFILE} and creates a regular expression that
406         * matches all the robots defined in the file. The regular expression is
407         * stored in the field {@link #robotRegex}.
408         * </p>
409         *
410         * @throws IOException
411         *             thrown if there is a problem reading the robot filter
412         * @throws FileNotFoundException
413         *             thrown if the robot filter is not found
414         */
415        private void loadRobotRegex() throws IOException, FileNotFoundException {
416                String[] lines = FileTools.getLinesFromFile(ROBOTFILTERFILE);
417                StringBuilder regex = new StringBuilder();
418                for (int i = 0; i < lines.length; i++) {
419                        regex.append("(.*" + lines[i] + ".*)");
420                        if (i != lines.length - 1) {
421                                regex.append('|');
422                        }
423                }
424                robotRegex = regex.toString();
425        }
426
427        /**
428         * <p>
429         * Checks whether an agent is a robot.
430         * </p>
431         *
432         * @param agent
433         *            agent that is checked
434         * @return true, if the agent is a robot; false otherwise
435         */
436        private boolean isRobot(String agent) {
437                return agent.matches(robotRegex);
438        }
439
440        /**
441         * <p>
442         * Parses the URI and extracts the GET variables that have been passed.
443         * </p>
444         *
445         * @param uri
446         *            URI that is parsed
447         * @return a list with all GET variables
448         * @throws URISyntaxException
449         *             thrown if one of the variables seems to indicate that the
450         *             request is a malicious attack on the web application
451         */
452        private List<String> extractGetVarsFromUri(URI uri)
453                        throws URISyntaxException {
454                List<String> getVars = new ArrayList<String>();
455                String query = uri.getQuery();
456                if (query != null) {
457                        String[] paramPairs = query.split("&");
458                        for (String paramPair : paramPairs) {
459                                String[] paramSplit = paramPair.split("=");
460                                if (!isBrokenVariable(paramSplit[0])) {
461                                        for (int i = 1; i < paramSplit.length; i++) {
462                                                checkForAttack(paramSplit[i]);
463                                        }
464                                        getVars.add(paramSplit[0]);
465                                }
466                        }
467                }
468                return getVars;
469        }
470
471        /**
472         * <p>
473         * Checks if a variable is broken.Currently, the check rather imprecise and
474         * checks only if the term &quot;and&quot; is part of the variable name.
475         * </p>
476         *
477         * @param var
478         *            variable that is checked
479         * @return true if the variable is broken, false otherwise
480         */
481        private boolean isBrokenVariable(String var) {
482                return var.contains("and");
483        }
484
485        /**
486         * <p>
487         * Checks if the variable name send with a request seems like an attack on the server.
488         * </p>
489         * @param value
490         * @throws URISyntaxException
491         */
492        private void checkForAttack(String value) throws URISyntaxException {
493                if (value.contains("UNION+") || value.contains("SELECT+")) {
494                        throw new URISyntaxException(value, "possible injection attack");
495                }
496        }
497}
Note: See TracBrowser for help on using the repository browser.