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