1/*
2 * Copyright (C) 2011 Google 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 are
6 * met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30
31#include "core/inspector/ContentSearchUtils.h"
32
33#include "bindings/core/v8/ScriptRegexp.h"
34#include "wtf/Vector.h"
35#include "wtf/text/StringBuilder.h"
36
37namespace blink {
38namespace ContentSearchUtils {
39
40namespace {
41// This should be kept the same as the one in front-end/utilities.js
42static const char regexSpecialCharacters[] = "[](){}+-*.,?\\^$|";
43}
44
45static String createSearchRegexSource(const String& text)
46{
47    StringBuilder result;
48    String specials(regexSpecialCharacters);
49
50    for (unsigned i = 0; i < text.length(); i++) {
51        if (specials.find(text[i]) != kNotFound)
52            result.append('\\');
53        result.append(text[i]);
54    }
55
56    return result.toString();
57}
58
59static Vector<pair<int, String> > getScriptRegexpMatchesByLines(const ScriptRegexp* regex, const String& text)
60{
61    Vector<pair<int, String> > result;
62    if (text.isEmpty())
63        return result;
64
65    OwnPtr<Vector<unsigned> > endings(lineEndings(text));
66    unsigned size = endings->size();
67    unsigned start = 0;
68    for (unsigned lineNumber = 0; lineNumber < size; ++lineNumber) {
69        unsigned lineEnd = endings->at(lineNumber);
70        String line = text.substring(start, lineEnd - start);
71        if (line.endsWith('\r'))
72            line = line.left(line.length() - 1);
73
74        int matchLength;
75        if (regex->match(line, 0, &matchLength) != -1)
76            result.append(pair<int, String>(lineNumber, line));
77
78        start = lineEnd + 1;
79    }
80    return result;
81}
82
83static PassRefPtr<TypeBuilder::Page::SearchMatch> buildObjectForSearchMatch(int lineNumber, const String& lineContent)
84{
85    return TypeBuilder::Page::SearchMatch::create()
86        .setLineNumber(lineNumber)
87        .setLineContent(lineContent)
88        .release();
89}
90
91PassOwnPtr<ScriptRegexp> createSearchRegex(const String& query, bool caseSensitive, bool isRegex)
92{
93    String regexSource = isRegex ? query : createSearchRegexSource(query);
94    return adoptPtr(new ScriptRegexp(regexSource, caseSensitive ? TextCaseSensitive : TextCaseInsensitive));
95}
96
97PassRefPtr<TypeBuilder::Array<TypeBuilder::Page::SearchMatch> > searchInTextByLines(const String& text, const String& query, const bool caseSensitive, const bool isRegex)
98{
99    RefPtr<TypeBuilder::Array<TypeBuilder::Page::SearchMatch> > result = TypeBuilder::Array<TypeBuilder::Page::SearchMatch>::create();
100
101    OwnPtr<ScriptRegexp> regex = ContentSearchUtils::createSearchRegex(query, caseSensitive, isRegex);
102    Vector<pair<int, String> > matches = getScriptRegexpMatchesByLines(regex.get(), text);
103
104    for (Vector<pair<int, String> >::const_iterator it = matches.begin(); it != matches.end(); ++it)
105        result->addItem(buildObjectForSearchMatch(it->first, it->second));
106
107    return result;
108}
109
110static String findMagicComment(const String& content, const String& name, MagicCommentType commentType, bool* deprecated = 0)
111{
112    ASSERT(name.find("=") == kNotFound);
113    if (deprecated)
114        *deprecated = false;
115
116    unsigned length = content.length();
117    unsigned nameLength = name.length();
118
119    size_t pos = length;
120    size_t equalSignPos = 0;
121    size_t closingCommentPos = 0;
122    while (true) {
123        pos = content.reverseFind(name, pos);
124        if (pos == kNotFound)
125            return String();
126
127        // Check for a /\/[\/*][@#][ \t]/ regexp (length of 4) before found name.
128        if (pos < 4)
129            return String();
130        pos -= 4;
131        if (content[pos] != '/')
132            continue;
133        if ((content[pos + 1] != '/' || commentType != JavaScriptMagicComment)
134            && (content[pos + 1] != '*' || commentType != CSSMagicComment))
135            continue;
136        if (content[pos + 2] != '#' && content[pos + 2] != '@')
137            continue;
138        if (content[pos + 3] != ' ' && content[pos + 3] != '\t')
139            continue;
140        equalSignPos = pos + 4 + nameLength;
141        if (equalSignPos < length && content[equalSignPos] != '=')
142            continue;
143        if (commentType == CSSMagicComment) {
144            closingCommentPos = content.find("*/", equalSignPos + 1);
145            if (closingCommentPos == kNotFound)
146                return String();
147        }
148
149        break;
150    }
151
152    if (deprecated && content[pos + 2] == '@')
153        *deprecated = true;
154
155    ASSERT(equalSignPos);
156    ASSERT(commentType != CSSMagicComment || closingCommentPos);
157    size_t urlPos = equalSignPos + 1;
158    String match = commentType == CSSMagicComment
159        ? content.substring(urlPos, closingCommentPos - urlPos)
160        : content.substring(urlPos);
161
162    size_t newLine = match.find("\n");
163    if (newLine != kNotFound)
164        match = match.substring(0, newLine);
165    match = match.stripWhiteSpace();
166
167    String disallowedChars("\"' \t");
168    for (unsigned i = 0; i < match.length(); ++i) {
169        if (disallowedChars.find(match[i]) != kNotFound)
170            return "";
171    }
172
173    return match;
174}
175
176String findSourceURL(const String& content, MagicCommentType commentType, bool* deprecated)
177{
178    return findMagicComment(content, "sourceURL", commentType, deprecated);
179}
180
181String findSourceMapURL(const String& content, MagicCommentType commentType, bool* deprecated)
182{
183    return findMagicComment(content, "sourceMappingURL", commentType, deprecated);
184}
185
186} // namespace ContentSearchUtils
187} // namespace blink
188
189