summaryrefslogtreecommitdiffstats
path: root/toolkit/components/search/tests/xpcshell/data/searchSuggestions.sjs
blob: 85f51795b6d18dac33cf5e666a19245160f7679d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

let { setTimeout } = ChromeUtils.importESModule(
  "resource://gre/modules/Timer.sys.mjs"
);
let { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");

Cu.importGlobalProperties(["TextEncoder"]);

/**
 * Provide search suggestions in the OpenSearch JSON format.
 */

function handleRequest(request, response) {
  // Get the query parameters from the query string.
  let query = parseQueryString(request.queryString);

  function convertToUtf8(str) {
    return String.fromCharCode(...new TextEncoder().encode(str));
  }

  function writeSuggestions(q, completions = []) {
    let jsonString = JSON.stringify([q, completions]);

    // This script must be evaluated as UTF-8 for this to write out the bytes of
    // the string in UTF-8.  If it's evaluated as Latin-1, the written bytes
    // will be the result of UTF-8-encoding the result-string *twice*, which
    // will break the "I ❤️" case further down.
    let stringOfUtf8Bytes = convertToUtf8(jsonString);

    response.write(stringOfUtf8Bytes);
  }

  /**
   * Sends `data` as suggestions directly. This is useful when testing rich
   * suggestions, which do not conform to the object shape sent by
   * writeSuggestions.
   *
   * @param {Array} data The data to send as suggestions.
   */
  function writeSuggestionsDirectly(data) {
    let jsonString = JSON.stringify(data);
    let stringOfUtf8Bytes = convertToUtf8(jsonString);
    response.setHeader("Content-Type", "application/json", false);
    response.write(stringOfUtf8Bytes);
  }

  response.setStatusLine(request.httpVersion, 200, "OK");

  let q = request.method == "GET" ? query.q : undefined;
  if (q == "cookie") {
    response.setHeader("Set-Cookie", "cookie=1");
    writeSuggestions(q);
  } else if (q == "no remote" || q == "no results") {
    writeSuggestions(q);
  } else if (q == "Query Mismatch") {
    writeSuggestions("This is an incorrect query string", ["some result"]);
  } else if (q == "Query Case Mismatch") {
    writeSuggestions(q.toUpperCase(), [q]);
  } else if (q == "") {
    writeSuggestions("", ["The server should never be sent an empty query"]);
  } else if (q && q.startsWith("mo")) {
    writeSuggestions(q, ["Mozilla", "modern", "mom"]);
  } else if (q && q.startsWith("I ❤️")) {
    writeSuggestions(q, ["I ❤️ Mozilla"]);
  } else if (q && q.startsWith("stü")) {
    writeSuggestions("st\\u00FC", ["stühle", "stüssy"]);
  } else if (q && q.startsWith("tailjunk ")) {
    writeSuggestionsDirectly([
      q,
      [q + " normal", q + " tail 1", q + " tail 2"],
      [],
      {
        "google:irrelevantparameter": [],
        "google:badformat": {
          "google:suggestdetail": [
            {},
            { mp: "… ", t: "tail 1" },
            { mp: "… ", t: "tail 2" },
          ],
        },
      },
    ]);
  } else if (q && q.startsWith("tailjunk few ")) {
    writeSuggestionsDirectly([
      q,
      [q + " normal", q + " tail 1", q + " tail 2"],
      [],
      {
        "google:irrelevantparameter": [],
        "google:badformat": {
          "google:suggestdetail": [{ mp: "… ", t: "tail 1" }],
        },
      },
    ]);
  } else if (q && q.startsWith("tailalt ")) {
    writeSuggestionsDirectly([
      q,
      [q + " normal", q + " tail 1", q + " tail 2"],
      {
        "google:suggestdetail": [
          {},
          { mp: "… ", t: "tail 1" },
          { mp: "… ", t: "tail 2" },
        ],
      },
    ]);
  } else if (q && q.startsWith("tail ")) {
    writeSuggestionsDirectly([
      q,
      [q + " normal", q + " tail 1", q + " tail 2"],
      [],
      {
        "google:irrelevantparameter": [],
        "google:suggestdetail": [
          {},
          { mp: "… ", t: "tail 1" },
          { mp: "… ", t: "tail 2" },
        ],
      },
    ]);
  } else if (q && q.startsWith("richempty ")) {
    writeSuggestionsDirectly([
      q,
      [q + " normal", q + " tail 1", q + " tail 2"],
      [],
      {
        "google:irrelevantparameter": [],
        "google:suggestdetail": [],
      },
    ]);
  } else if (q && q.startsWith("letter ")) {
    let letters = [];
    for (
      let charCode = "A".charCodeAt();
      charCode <= "Z".charCodeAt();
      charCode++
    ) {
      letters.push("letter " + String.fromCharCode(charCode));
    }
    writeSuggestions(q, letters);
  } else if (q && q.startsWith("HTTP ")) {
    response.setStatusLine(request.httpVersion, q.replace("HTTP ", ""), q);
    writeSuggestions(q, [q]);
  } else if (q && q.startsWith("delay")) {
    // Delay the response by delayMs milliseconds. 200ms is the default, less
    // than the timeout but hopefully enough to abort before completion.
    let match = /^delay([0-9]+)/.exec(q);
    let delayMs = match ? parseInt(match[1]) : 200;
    response.processAsync();
    writeSuggestions(q, [q]);
    setTimeout(() => response.finish(), delayMs);
  } else if (q && q.startsWith("slow ")) {
    // Delay the response by 10 seconds so the client timeout is reached.
    response.processAsync();
    writeSuggestions(q, [q]);
    setTimeout(() => response.finish(), 10000);
  } else if (request.method == "POST") {
    // This includes headers, not just the body
    let requestText = NetUtil.readInputStreamToString(
      request.bodyInputStream,
      request.bodyInputStream.available()
    );
    // Only use the last line which contains the encoded params
    let requestLines = requestText.split("\n");
    let postParams = parseQueryString(requestLines[requestLines.length - 1]);
    writeSuggestions(postParams.q, ["Mozilla", "modern", "mom"]);
  } else {
    response.setStatusLine(request.httpVersion, 404, "Not Found");
  }
}

function parseQueryString(queryString) {
  let query = {};
  queryString.split("&").forEach(function (val) {
    let [name, value] = val.split("=");
    query[name] = decodeURIComponent(value).replace(/[+]/g, " ");
  });
  return query;
}