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

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