WebCore headers should not be included relatively within dependent projects
[WebKit-https.git] / Source / WebKit / NetworkProcess / capture / NetworkCaptureManager.cpp
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkCaptureManager.h"
28
29 #if ENABLE(NETWORK_CAPTURE)
30
31 #include "NetworkCaptureLogging.h"
32 #include "NetworkCaptureResource.h"
33 #include <WebCore/ResourceRequest.h>
34 #include <WebCore/URL.h>
35 #include <algorithm>
36 #include <iterator>
37 #include <limits>
38 #include <wtf/MD5.h>
39 #include <wtf/NeverDestroyed.h>
40 #include <wtf/text/Base64.h>
41 #include <wtf/text/StringBuilder.h>
42
43 #define DEBUG_CLASS Manager
44
45 using namespace WebCore::FileSystem;
46
47 namespace WebKit {
48 namespace NetworkCapture {
49
50 static const char* kDirNameRecordReplay = "WebKitPerf/record_replay";
51 static const char* kDirNameResources = "resources";
52 static const char* kFileNameReportLoad = "report_load.txt";
53 static const char* kFileNameReportRecord = "report_record.txt";
54 static const char* kFileNameReportReplay = "report_replay.txt";
55
56 static int kMaxMatch = std::numeric_limits<int>::max();
57 static int kMinMatch = std::numeric_limits<int>::min();
58
59 Manager& Manager::singleton()
60 {
61     static NeverDestroyed<Manager> instance;
62     return instance;
63 }
64
65 void Manager::initialize(const String& recordReplayMode, const String& recordReplayCacheLocation)
66 {
67     if (equalIgnoringASCIICase(recordReplayMode, "record")) {
68         DEBUG_LOG("Initializing: recording mode");
69         m_recordReplayMode = Record;
70     } else if (equalIgnoringASCIICase(recordReplayMode, "replay")) {
71         DEBUG_LOG("Initializing: replay mode");
72         m_recordReplayMode = Replay;
73     } else {
74         DEBUG_LOG("Initializing: disabled");
75         m_recordReplayMode = Disabled;
76     }
77
78     m_recordReplayCacheLocation = pathByAppendingComponent(recordReplayCacheLocation, kDirNameRecordReplay);
79     DEBUG_LOG("Cache location = " STRING_SPECIFIER, DEBUG_STR(m_recordReplayCacheLocation));
80
81     if (isRecording()) {
82         m_recordFileHandle = WebCore::FileHandle(reportRecordPath(), FileOpenMode::Write);
83     } else if (isReplaying()) {
84         m_recordFileHandle = WebCore::FileHandle(reportRecordPath(), FileOpenMode::Read);
85         m_loadFileHandle = WebCore::FileHandle(reportLoadPath(), FileOpenMode::Write);
86         m_replayFileHandle = WebCore::FileHandle(reportReplayPath(), FileOpenMode::Write);
87         loadResources();
88     }
89 }
90
91 void Manager::terminate()
92 {
93     m_loadFileHandle.close();
94     m_recordFileHandle.close();
95     m_replayFileHandle.close();
96 }
97
98 Resource* Manager::findMatch(const WebCore::ResourceRequest& request)
99 {
100     DEBUG_LOG_VERBOSE("URL = " STRING_SPECIFIER, DEBUG_STR(request.url().string()));
101
102     auto bestMatch = findExactMatch(request);
103     if (!bestMatch)
104         bestMatch = findBestFuzzyMatch(request);
105
106 #if CAPTURE_INTERNAL_DEBUGGING
107     if (!bestMatch)
108         DEBUG_LOG("Could not find match for: " STRING_SPECIFIER, DEBUG_STR(request.url().string()));
109     else if (request.url() == bestMatch->url())
110         DEBUG_LOG("Found exact match for: " STRING_SPECIFIER, DEBUG_STR(request.url().string()));
111     else {
112         DEBUG_LOG("Found fuzzy match for: " STRING_SPECIFIER, DEBUG_STR(request.url().string()));
113         DEBUG_LOG("       replaced with : " STRING_SPECIFIER, DEBUG_STR(bestMatch->url().string()));
114     }
115 #endif
116
117     return bestMatch;
118 }
119
120 Resource* Manager::findExactMatch(const WebCore::ResourceRequest& request)
121 {
122     const auto& url = request.url();
123     auto lower = std::lower_bound(std::begin(m_cachedResources), std::end(m_cachedResources), url, [](auto& resource, const auto& url) {
124         return WTF::codePointCompareLessThan(resource.url().string(), url.string());
125     });
126
127     if (lower != std::end(m_cachedResources) && lower->url() == url) {
128         DEBUG_LOG_VERBOSE("Found exact match: " STRING_SPECIFIER, DEBUG_STR(lower->url().string()));
129         return &*lower;
130     }
131
132     return nullptr;
133 }
134
135 Resource* Manager::findBestFuzzyMatch(const WebCore::ResourceRequest& request)
136 {
137     const auto& url = request.url();
138     const auto& urlIdentifyingCommonDomain = Manager::urlIdentifyingCommonDomain(url);
139
140     const auto& lower = std::lower_bound(std::begin(m_cachedResources), std::end(m_cachedResources), urlIdentifyingCommonDomain, [](auto& resource, const auto& urlIdentifyingCommonDomain) {
141         return WTF::codePointCompareLessThan(resource.urlIdentifyingCommonDomain(), urlIdentifyingCommonDomain);
142     });
143     const auto& upper = std::upper_bound(lower, std::end(m_cachedResources), urlIdentifyingCommonDomain, [](const auto& urlIdentifyingCommonDomain, auto& resource) {
144         return WTF::codePointCompareLessThan(urlIdentifyingCommonDomain, resource.urlIdentifyingCommonDomain());
145     });
146
147     Resource* bestMatch = nullptr;
148     int bestScore = kMinMatch;
149     const auto& requestParameters = WebCore::URLParser::parseURLEncodedForm(url.query());
150     for (auto iResource = lower; iResource != upper; ++iResource) {
151         int thisScore = fuzzyMatchURLs(url, requestParameters, iResource->url(), iResource->queryParameters());
152         // TODO: Consider ignoring any matches < 0 as being too different.
153         if (bestScore < thisScore) {
154             DEBUG_LOG("New best match (%d): " STRING_SPECIFIER, thisScore, DEBUG_STR(iResource->url().string()));
155             bestScore = thisScore;
156             bestMatch = &*iResource;
157             if (bestScore == kMaxMatch)
158                 break;
159         }
160     }
161
162     return bestMatch;
163 }
164
165 // TODO: Convert to an interface based on ResourceRequest so that we can do
166 // deeper matching.
167
168 int Manager::fuzzyMatchURLs(const WebCore::URL& requestURL, const WebCore::URLParser::URLEncodedForm& requestParameters, const WebCore::URL& resourceURL, const WebCore::URLParser::URLEncodedForm& resourceParameters)
169 {
170     // TODO: consider requiring that any trailing suffixes (e.g., ".js",
171     // ".png", ".css", ".html", etc.) should be an exact match.
172
173     // We do fuzzy matching on the path and query parameters. So let's first
174     // make sure that all the other parts are equal.
175
176     // If scheme, host, and port don't all match, return this as the "worst"
177     // match.
178
179     if (!protocolHostAndPortAreEqual(requestURL, resourceURL)) {
180         DEBUG_LOG("Scheme/host/port mismatch: " STRING_SPECIFIER " != " STRING_SPECIFIER, DEBUG_STR(requestURL.string()), DEBUG_STR(resourceURL.string()));
181         return kMinMatch;
182     }
183
184     // If fragments don't match, return this as the "worst" match.
185
186     if (requestURL.fragmentIdentifier() != resourceURL.fragmentIdentifier()) {
187         DEBUG_LOG("Fragments mismatch: " STRING_SPECIFIER " != " STRING_SPECIFIER, DEBUG_STR(requestURL.string()), DEBUG_STR(resourceURL.string()));
188         return kMinMatch;
189     }
190
191     DEBUG_LOG("Fuzzy matching:");
192     DEBUG_LOG("   : " STRING_SPECIFIER, DEBUG_STR(requestURL.string()));
193     DEBUG_LOG("   : " STRING_SPECIFIER, DEBUG_STR(resourceURL.string()));
194
195     // Compare the path components and the query parameters. Score each partial
196     // match as +4, each mismatch as -1, and each missing component as -1.
197     //
198     // Note that at the current time these values are rather arbitrary and
199     // could fine-tuned.
200
201     const int kPathMatchScore = 4;
202     const int kPathMismatchScore = -1;
203     const int kPathMissingScore = -1;
204     const int kParameterMatchScore = 4;
205     const int kParameterMismatchScore = -1;
206     const int kParameterMissingScore = -1;
207
208     int score = 0;
209
210     // Quantize the differences in URL paths.
211     //
212     // The approach here is to increase our score for each matching path
213     // component, and to subtract for each differing component as well as for
214     // components that exist in one path but not the other.
215
216     const auto& requestPath = requestURL.path();
217     const auto& resourcePath = resourceURL.path();
218
219     Vector<String> requestPathComponents, resourcePathComponents;
220     requestPath.split('/', requestPathComponents);
221     resourcePath.split('/', resourcePathComponents);
222
223     auto updatedIterators = std::mismatch(
224         std::begin(requestPathComponents), std::end(requestPathComponents),
225         std::begin(resourcePathComponents), std::end(resourcePathComponents));
226
227     auto matchingDistance = std::distance(std::begin(requestPathComponents), updatedIterators.first);
228     auto requestPathMismatchDistance = std::distance(updatedIterators.first, std::end(requestPathComponents));
229     auto resourcePathMismatchDistance = std::distance(updatedIterators.second, std::end(resourcePathComponents));
230     decltype(matchingDistance) mismatchingDistance;
231     decltype(matchingDistance) missingDistance;
232     if (requestPathMismatchDistance < resourcePathMismatchDistance) {
233         mismatchingDistance = requestPathMismatchDistance;
234         missingDistance = resourcePathMismatchDistance - requestPathMismatchDistance;
235     } else {
236         mismatchingDistance = resourcePathMismatchDistance;
237         missingDistance = requestPathMismatchDistance - resourcePathMismatchDistance;
238     }
239
240     DEBUG_LOG("Path matching results: matching = %d, mismatching = %d, missing = %d",
241         static_cast<int>(matchingDistance),
242         static_cast<int>(mismatchingDistance),
243         static_cast<int>(missingDistance));
244
245     score += matchingDistance * kPathMatchScore
246         + mismatchingDistance * kPathMismatchScore
247         + missingDistance * kPathMissingScore;
248     DEBUG_LOG("Score = %d", score);
249
250     // Quantize the differences in query parameters.
251     //
252     // The approach here is to walk lock-step over the two sets of query
253     // parameters. For each pair of parameters for each URL, we compare their
254     // names and values. If the names and values match, we add a high score. If
255     // just the names match, we add a lower score.
256     //
257     // If the names don't match, we then assume that some intervening query
258     // parameters have been added to one or the other URL. We therefore try to
259     // sync up the iterators used to traverse the query parameter collections
260     // so that they're again pointing to parameters with the same names. We
261     // first start scanning forward down the query parameters for one URL,
262     // looking for one with the same name as the one we're on in the other URL.
263     // If that doesn't turn up a match, we reverse the roles of the query
264     // parameters perform the same process of scanning forward. If neither of
265     // these scans produces a match, we figure that each query parameter we're
266     // looking at from each of the query parameter collections is unique. We
267     // deduct points from the overall score and move on to the next query
268     // parameters in each set.
269     //
270     // If, on the other hand, the forward-scanning does turn up a match, we
271     // adjust out iterators so that they're now again pointing to query
272     // parameters with the same name. This synchronization involves skipping
273     // over any intervening query parameters in one collection or the other.
274     // The assumption here is that these intervening query parameters are
275     // insertions that exist in one URL but not the other. We treat them as
276     // such, subtracting from the overall score for each one. However, this
277     // assumption might easily be incorrect. It might be that the query
278     // parameters that we're skipping over in one URL might exist in the other
279     // URL. If so, then we are foregoing the possibility of using those matches
280     // to increase the overall match score between the two URLs.
281     //
282     // To address this problem, we might want to consider sorting the query
283     // parameters by their names. However, doing this may cause problems if the
284     // order of the parameters is significant. So if we decide to take the
285     // approach of sorting the parameters, keep in mind this possible drawback.
286
287     auto requestParameter = std::begin(requestParameters);
288     auto resourceParameter = std::begin(resourceParameters);
289
290     for (; requestParameter != std::end(requestParameters) && resourceParameter != std::end(resourceParameters); ++requestParameter, ++resourceParameter) {
291         if (requestParameter->key == resourceParameter->key) {
292 #if CAPTURE_INTERNAL_DEBUGGING
293             if (requestParameter->value == resourceParameter->value)
294                 DEBUG_LOG("Matching parameter names and values: \"" STRING_SPECIFIER "\" = \"" STRING_SPECIFIER "\"", DEBUG_STR(requestParameter->first), DEBUG_STR(requestParameter->second));
295             else
296                 DEBUG_LOG("Mismatching parameter values: \"" STRING_SPECIFIER "\" = \"" STRING_SPECIFIER "\" vs. \"" STRING_SPECIFIER "\"", DEBUG_STR(requestParameter->first), DEBUG_STR(requestParameter->second), DEBUG_STR(resourceParameter->second));
297 #endif
298             score += (requestParameter->value == resourceParameter->value) ? kParameterMatchScore : kParameterMismatchScore;
299             DEBUG_LOG("Score = %d", score);
300         } else {
301             DEBUG_LOG("Mismatching parameter names: " STRING_SPECIFIER ", " STRING_SPECIFIER, DEBUG_STR(requestParameter->first), DEBUG_STR(resourceParameter->first));
302
303             const auto scanForwardForMatch = [&score, kParameterMatchScore, kParameterMismatchScore, kParameterMissingScore](const auto& fixedIter, auto& scanningIter, const auto& scannerEnd) {
304                 auto scanner = scanningIter;
305                 while (scanner != scannerEnd && scanner->key != fixedIter->key)
306                     ++scanner;
307                 if (scanner == scannerEnd)
308                     return false;
309                 DEBUG_LOG("Skipping past %d non-matching parameter names", static_cast<int>(std::distance(scanningIter, scanner)));
310                 score += kParameterMissingScore * std::distance(scanningIter, scanner);
311                 DEBUG_LOG("Score = %d", score);
312 #if CAPTURE_INTERNAL_DEBUGGING
313                 if (fixedIter->second == scanner->second)
314                     DEBUG_LOG("Matching parameter names and values: \"" STRING_SPECIFIER "\" = \"" STRING_SPECIFIER "\"", DEBUG_STR(fixedIter->first), DEBUG_STR(fixedIter->second));
315                 else
316                     DEBUG_LOG("Mismatching parameter values: \"" STRING_SPECIFIER "\" = \"" STRING_SPECIFIER "\" vs. \"" STRING_SPECIFIER "\"", DEBUG_STR(fixedIter->first), DEBUG_STR(fixedIter->second), DEBUG_STR(scanner->second));
317 #endif
318                 score += (fixedIter->value == scanner->value) ? kParameterMatchScore : kParameterMismatchScore;
319                 DEBUG_LOG("Score = %d", score);
320                 scanningIter = scanner;
321                 return true;
322             };
323
324             if (!scanForwardForMatch(requestParameter, resourceParameter, std::end(resourceParameters))) {
325                 if (!scanForwardForMatch(resourceParameter, requestParameter, std::end(requestParameters))) {
326                     DEBUG_LOG("Unmatched parameter: " STRING_SPECIFIER "=" STRING_SPECIFIER, DEBUG_STR(requestParameter->first), DEBUG_STR(requestParameter->second));
327                     DEBUG_LOG("Unmatched parameter: " STRING_SPECIFIER "=" STRING_SPECIFIER, DEBUG_STR(resourceParameter->first), DEBUG_STR(resourceParameter->second));
328                     score += kParameterMissingScore + kParameterMissingScore;
329                     DEBUG_LOG("Score = %d", score);
330                 }
331             }
332         }
333     }
334
335     DEBUG_LOG("Adjusting for trailing parameters");
336     score += kParameterMissingScore
337         * (std::distance(requestParameter, std::end(requestParameters))
338             + std::distance(resourceParameter, std::end(resourceParameters)));
339     DEBUG_LOG("Score = %d", score);
340
341     return score;
342 }
343
344 void Manager::loadResources()
345 {
346     auto lines = readFile(reportRecordPath());
347     if (!lines)
348         return;
349
350     for (const auto& line : *lines) {
351         if (line.size() != 2) {
352             DEBUG_LOG_ERROR("line.size == %d", (int) line.size());
353             continue;
354         }
355
356         Resource newResource(hashToPath(line[0]));
357         m_cachedResources.append(WTFMove(newResource));
358     }
359
360     std::sort(std::begin(m_cachedResources), std::end(m_cachedResources), [](auto& left, auto& right) {
361         return WTF::codePointCompareLessThan(left.url().string(), right.url().string());
362     });
363
364     for (auto& resource : m_cachedResources)
365         logLoadedResource(resource);
366 }
367
368 String Manager::reportLoadPath()
369 {
370     return pathByAppendingComponent(m_recordReplayCacheLocation, kFileNameReportLoad);
371 }
372
373 String Manager::reportRecordPath()
374 {
375     return pathByAppendingComponent(m_recordReplayCacheLocation, kFileNameReportRecord);
376 }
377
378 String Manager::reportReplayPath()
379 {
380     return pathByAppendingComponent(m_recordReplayCacheLocation, kFileNameReportReplay);
381 }
382
383 String Manager::requestToPath(const WebCore::ResourceRequest& request)
384 {
385     // TODO: come up with a more comprehensive hash that includes HTTP method
386     // and possibly other values (such as headers).
387
388     const auto& hash = stringToHash(request.url().string());
389     const auto& path = hashToPath(hash);
390     return path;
391 }
392
393 String Manager::stringToHash(const String& s)
394 {
395     WTF::MD5 md5;
396     if (s.characters8())
397         md5.addBytes(static_cast<const uint8_t*>(s.characters8()), s.length());
398     else
399         md5.addBytes(reinterpret_cast<const uint8_t*>(s.characters16()), 2 * s.length());
400
401     WTF::MD5::Digest digest;
402     md5.checksum(digest);
403
404     return WTF::base64URLEncode(&digest[0], WTF::MD5::hashSize);
405 }
406
407 String Manager::hashToPath(const String& hash)
408 {
409     auto hashHead = hash.substring(0, 2);
410     auto hashTail = hash.substring(2);
411
412     StringBuilder fileName;
413     fileName.append(hashTail);
414     fileName.appendLiteral(".data");
415
416     auto path = pathByAppendingComponent(m_recordReplayCacheLocation, kDirNameResources);
417     path = pathByAppendingComponent(path, hashHead);
418     path = pathByAppendingComponent(path, fileName.toString());
419
420     return path;
421 }
422
423 String Manager::urlIdentifyingCommonDomain(const WebCore::URL& url)
424 {
425     return url.protocolHostAndPort();
426 }
427
428 void Manager::logRecordedResource(const WebCore::ResourceRequest& request)
429 {
430     // Log network resources as they are cached to disk.
431
432     const auto& url = request.url();
433     m_recordFileHandle.printf("%s %s\n", DEBUG_STR(stringToHash(url.string())), DEBUG_STR(url.string()));
434 }
435
436 void Manager::logLoadedResource(Resource& resource)
437 {
438     // Log cached resources as they are loaded from disk.
439
440     m_loadFileHandle.printf("%s\n", DEBUG_STR(resource.url().string()));
441 }
442
443 void Manager::logPlayedBackResource(const WebCore::ResourceRequest& request, bool wasCacheMiss)
444 {
445     // Log network resources that are requested during replay.
446
447     const auto& url = request.url();
448
449     if (wasCacheMiss)
450         DEBUG_LOG("Cache miss: URL = " STRING_SPECIFIER, DEBUG_STR(url.string()));
451     else
452         DEBUG_LOG("Cache hit:  URL = " STRING_SPECIFIER, DEBUG_STR(url.string()));
453
454     m_replayFileHandle.printf("%s %s\n", wasCacheMiss ? "miss" : "hit ", DEBUG_STR(url.string()));
455 }
456
457 WebCore::FileHandle Manager::openCacheFile(const String& filePath, FileOpenMode mode)
458 {
459     // If we can trivially open the file, then do that and return the new file
460     // handle.
461
462     auto fileHandle = WebCore::FileHandle(filePath, mode);
463     if (fileHandle.open())
464         return fileHandle;
465
466     // If we're opening the file for writing (including appending), then try
467     // again after making sure all intermediate directories have been created.
468
469     if (mode != FileOpenMode::Read) {
470         const auto& parentDir = directoryName(filePath);
471         if (!makeAllDirectories(parentDir)) {
472             DEBUG_LOG_ERROR("Error %d trying to create intermediate directories: " STRING_SPECIFIER, errno, DEBUG_STR(parentDir));
473             return fileHandle;
474         }
475
476         fileHandle = WebCore::FileHandle(filePath, mode);
477         if (fileHandle.open())
478             return fileHandle;
479     }
480
481     // Could not open the file. Log the error and leave, returning the invalid
482     // file handle.
483
484     if (mode == FileOpenMode::Read)
485         DEBUG_LOG_ERROR("Error %d trying to open " STRING_SPECIFIER " for reading", errno, DEBUG_STR(filePath));
486     else
487         DEBUG_LOG_ERROR("Error %d trying to open " STRING_SPECIFIER " for writing", errno, DEBUG_STR(filePath));
488
489     return fileHandle;
490 }
491
492 std::optional<Vector<Vector<String>>> Manager::readFile(const String& filePath)
493 {
494     bool success = false;
495     MappedFileData file(filePath, success);
496     if (!success)
497         return std::nullopt;
498
499     Vector<Vector<String>> lines;
500     auto begin = static_cast<const uint8_t*>(file.data());
501     auto end = begin + file.size();
502
503     Vector<String> line;
504     while (getLine(begin, end, line))
505         lines.append(WTFMove(line));
506
507     return WTFMove(lines);
508 }
509
510 bool Manager::getLine(uint8_t const *& p, uint8_t const * const end, Vector<String>& line)
511 {
512     // NB: Returns true if there may be more data to get, false if we've hit
513     // the end of the buffer.
514
515     DEBUG_LOG_VERBOSE("Getting a line");
516
517     line.clear();
518
519     if (p == end) {
520         DEBUG_LOG_VERBOSE("Iterator at end; returning false");
521         return false;
522     }
523
524     String word;
525     while (getWord(p, end, word)) {
526         if (!word.isEmpty()) {
527             DEBUG_LOG_VERBOSE("Adding word: " STRING_SPECIFIER, DEBUG_STR(word));
528             line.append(word);
529         }
530     }
531
532     return true;
533 }
534
535 bool Manager::getWord(uint8_t const *& p, uint8_t const * const end, String& word)
536 {
537     // NB: Returns true if a (possibly empty) word was found and there may be
538     // more, false if we've hit the end of line or buffer.
539
540     DEBUG_LOG_VERBOSE("Getting a word");
541
542     if (p == end) {
543         DEBUG_LOG_VERBOSE("Iterator at end; returning false");
544         return false;
545     }
546
547     if (*p == '\n') {
548         DEBUG_LOG_VERBOSE("Iterator hit EOL; returning false");
549         ++p;
550         return false;
551     }
552
553     bool escaping = false;
554     bool ignoring = false;
555
556     word = String();
557
558     DEBUG_LOG_VERBOSE("Iterating");
559
560     for ( ; p != end; ++p) {
561         if (ignoring) {
562             if (*p == '\n')
563                 break;
564         } else if (escaping) {
565             word.append(*p);
566             escaping = false;
567         } else if (*p == '#') {
568             ignoring = true;
569         } else if (*p == '\\') {
570             escaping = true;
571         } else if (*p == ' ') {
572             if (!word.isEmpty())
573                 break;
574         } else if (*p == '\n')
575             break;
576         else
577             word.append(*p);
578     }
579
580     return true;
581 }
582
583 } // namespace NetworkCapture
584 } // namespace WebKit
585
586 #endif // ENABLE(NETWORK_CAPTURE)