source: trunk/autoquest-plugin-php/src/main/java/de/ugoe/cs/autoquest/plugin/php/WeblogParser.java @ 922

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