1// Copyright 2016 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/inspector/search-util.h"
6
7#include "src/inspector/protocol/Protocol.h"
8#include "src/inspector/v8-inspector-impl.h"
9#include "src/inspector/v8-inspector-session-impl.h"
10#include "src/inspector/v8-regex.h"
11
12namespace v8_inspector {
13
14namespace {
15
16String16 findMagicComment(const String16& content, const String16& name,
17                          bool multiline) {
18  DCHECK(name.find("=") == String16::kNotFound);
19  size_t length = content.length();
20  size_t nameLength = name.length();
21
22  size_t pos = length;
23  size_t equalSignPos = 0;
24  size_t closingCommentPos = 0;
25  while (true) {
26    pos = content.reverseFind(name, pos);
27    if (pos == String16::kNotFound) return String16();
28
29    // Check for a /\/[\/*][@#][ \t]/ regexp (length of 4) before found name.
30    if (pos < 4) return String16();
31    pos -= 4;
32    if (content[pos] != '/') continue;
33    if ((content[pos + 1] != '/' || multiline) &&
34        (content[pos + 1] != '*' || !multiline))
35      continue;
36    if (content[pos + 2] != '#' && content[pos + 2] != '@') continue;
37    if (content[pos + 3] != ' ' && content[pos + 3] != '\t') continue;
38    equalSignPos = pos + 4 + nameLength;
39    if (equalSignPos < length && content[equalSignPos] != '=') continue;
40    if (multiline) {
41      closingCommentPos = content.find("*/", equalSignPos + 1);
42      if (closingCommentPos == String16::kNotFound) return String16();
43    }
44
45    break;
46  }
47
48  DCHECK(equalSignPos);
49  DCHECK(!multiline || closingCommentPos);
50  size_t urlPos = equalSignPos + 1;
51  String16 match = multiline
52                       ? content.substring(urlPos, closingCommentPos - urlPos)
53                       : content.substring(urlPos);
54
55  size_t newLine = match.find("\n");
56  if (newLine != String16::kNotFound) match = match.substring(0, newLine);
57  match = match.stripWhiteSpace();
58
59  for (size_t i = 0; i < match.length(); ++i) {
60    UChar c = match[i];
61    if (c == '"' || c == '\'' || c == ' ' || c == '\t') return "";
62  }
63
64  return match;
65}
66
67String16 createSearchRegexSource(const String16& text) {
68  String16Builder result;
69
70  for (size_t i = 0; i < text.length(); i++) {
71    UChar c = text[i];
72    if (c == '[' || c == ']' || c == '(' || c == ')' || c == '{' || c == '}' ||
73        c == '+' || c == '-' || c == '*' || c == '.' || c == ',' || c == '?' ||
74        c == '\\' || c == '^' || c == '$' || c == '|') {
75      result.append('\\');
76    }
77    result.append(c);
78  }
79
80  return result.toString();
81}
82
83std::unique_ptr<std::vector<size_t>> lineEndings(const String16& text) {
84  std::unique_ptr<std::vector<size_t>> result(new std::vector<size_t>());
85
86  const String16 lineEndString = "\n";
87  size_t start = 0;
88  while (start < text.length()) {
89    size_t lineEnd = text.find(lineEndString, start);
90    if (lineEnd == String16::kNotFound) break;
91
92    result->push_back(lineEnd);
93    start = lineEnd + 1;
94  }
95  result->push_back(text.length());
96
97  return result;
98}
99
100std::vector<std::pair<int, String16>> scriptRegexpMatchesByLines(
101    const V8Regex& regex, const String16& text) {
102  std::vector<std::pair<int, String16>> result;
103  if (text.isEmpty()) return result;
104
105  std::unique_ptr<std::vector<size_t>> endings(lineEndings(text));
106  size_t size = endings->size();
107  size_t start = 0;
108  for (size_t lineNumber = 0; lineNumber < size; ++lineNumber) {
109    size_t lineEnd = endings->at(lineNumber);
110    String16 line = text.substring(start, lineEnd - start);
111    if (line.length() && line[line.length() - 1] == '\r')
112      line = line.substring(0, line.length() - 1);
113
114    int matchLength;
115    if (regex.match(line, 0, &matchLength) != -1)
116      result.push_back(std::pair<int, String16>(lineNumber, line));
117
118    start = lineEnd + 1;
119  }
120  return result;
121}
122
123std::unique_ptr<protocol::Debugger::SearchMatch> buildObjectForSearchMatch(
124    int lineNumber, const String16& lineContent) {
125  return protocol::Debugger::SearchMatch::create()
126      .setLineNumber(lineNumber)
127      .setLineContent(lineContent)
128      .build();
129}
130
131std::unique_ptr<V8Regex> createSearchRegex(V8InspectorImpl* inspector,
132                                           const String16& query,
133                                           bool caseSensitive, bool isRegex) {
134  String16 regexSource = isRegex ? query : createSearchRegexSource(query);
135  return std::unique_ptr<V8Regex>(
136      new V8Regex(inspector, regexSource, caseSensitive));
137}
138
139}  // namespace
140
141std::vector<std::unique_ptr<protocol::Debugger::SearchMatch>>
142searchInTextByLinesImpl(V8InspectorSession* session, const String16& text,
143                        const String16& query, const bool caseSensitive,
144                        const bool isRegex) {
145  std::unique_ptr<V8Regex> regex = createSearchRegex(
146      static_cast<V8InspectorSessionImpl*>(session)->inspector(), query,
147      caseSensitive, isRegex);
148  std::vector<std::pair<int, String16>> matches =
149      scriptRegexpMatchesByLines(*regex.get(), text);
150
151  std::vector<std::unique_ptr<protocol::Debugger::SearchMatch>> result;
152  for (const auto& match : matches)
153    result.push_back(buildObjectForSearchMatch(match.first, match.second));
154  return result;
155}
156
157String16 findSourceURL(const String16& content, bool multiline) {
158  return findMagicComment(content, "sourceURL", multiline);
159}
160
161String16 findSourceMapURL(const String16& content, bool multiline) {
162  return findMagicComment(content, "sourceMappingURL", multiline);
163}
164
165}  // namespace v8_inspector
166