summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/test/browser/crashreport.sjs
blob: 908455c9e6be3c29b05124b00fa496ed6207fef3 (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
const CC = Components.Constructor;

const BinaryInputStream = CC(
  "@mozilla.org/binaryinputstream;1",
  "nsIBinaryInputStream",
  "setInputStream"
);

function parseHeaders(data, start) {
  let headers = {};

  while (true) {
    let end = data.indexOf("\r\n", start);
    if (end == -1) {
      end = data.length;
    }
    let line = data.substring(start, end);
    start = end + 2;
    if (line == "") {
      // empty line, we're done
      break;
    }

    //XXX: this doesn't handle multi-line headers. do we care?
    let [name, value] = line.split(":");
    //XXX: not normalized, should probably use nsHttpHeaders or something
    headers[name] = value.trimLeft();
  }
  return [headers, start];
}

function parseMultipartForm(request) {
  let boundary = null;
  // See if this is a multipart/form-data request, and if so, find the
  // boundary string
  if (request.hasHeader("Content-Type")) {
    var contenttype = request.getHeader("Content-Type");
    var bits = contenttype.split(";");
    if (bits[0] == "multipart/form-data") {
      for (var i = 1; i < bits.length; i++) {
        var b = bits[i].trimLeft();
        if (b.indexOf("boundary=") == 0) {
          // grab everything after boundary=
          boundary = "--" + b.substring(9);
          break;
        }
      }
    }
  }
  if (boundary == null) {
    return null;
  }

  let body = new BinaryInputStream(request.bodyInputStream);
  let avail;
  let bytes = [];
  while ((avail = body.available()) > 0) {
    let readBytes = body.readByteArray(avail);
    for (let b of readBytes) {
      bytes.push(b);
    }
  }
  let data = "";
  for (let b of bytes) {
    data += String.fromCharCode(b);
  }
  let formData = {};
  let start = 0;
  while (true) {
    // read first line
    let end = data.indexOf("\r\n", start);
    if (end == -1) {
      end = data.length;
    }

    let line = data.substring(start, end);
    // look for closing boundary delimiter line
    if (line == boundary + "--") {
      break;
    }

    if (line != boundary) {
      dump("expected boundary line but didn't find it!");
      break;
    }

    // parse headers
    start = end + 2;
    let headers = null;
    [headers, start] = parseHeaders(data, start);

    // find next boundary string
    end = data.indexOf("\r\n" + boundary, start);
    if (end == -1) {
      dump("couldn't find next boundary string\n");
      break;
    }

    // read part data, stick in formData using Content-Disposition header
    let part = data.substring(start, end);
    start = end + 2;

    if ("Content-Disposition" in headers) {
      let bits = headers["Content-Disposition"].split(";");
      if (bits[0] == "form-data") {
        for (let i = 0; i < bits.length; i++) {
          let b = bits[i].trimLeft();
          if (b.indexOf("name=") == 0) {
            //TODO: handle non-ascii here?
            let name = b.substring(6, b.length - 1);
            //TODO: handle multiple-value properties?
            if (
              "Content-Type" in headers &&
              headers["Content-Type"] == "application/json"
            ) {
              formData = Object.assign(formData, JSON.parse(part));
            } else {
              formData[name] = part;
            }
          }
          //TODO: handle filename= ?
          //TODO: handle multipart/mixed for multi-file uploads?
        }
      }
    }
  }
  return formData;
}

function handleRequest(request, response) {
  if (request.method == "GET") {
    let id = null;
    for (let p of request.queryString.split("&")) {
      let [key, value] = p.split("=");
      if (key == "id") {
        id = value;
      }
    }
    if (id == null) {
      response.setStatusLine(request.httpVersion, 400, "Bad Request");
      response.write("Missing id parameter");
    } else {
      let data = getState(id);
      if (data == "") {
        response.setStatusLine(request.httpVersion, 404, "Not Found");
        response.write("Not Found");
      } else {
        response.setHeader("Content-Type", "text/plain", false);
        response.write(data);
      }
    }
  } else if (request.method == "POST") {
    let formData = parseMultipartForm(request);

    if (formData && "upload_file_minidump" in formData) {
      response.setHeader("Content-Type", "text/plain", false);

      let uuid = Services.uuid.generateUUID().toString();
      // ditch the {}, add bp- prefix
      uuid = "bp-" + uuid.substring(1, uuid.length - 1);

      let d = JSON.stringify(formData);
      //dump('saving crash report ' + uuid + ': ' + d + '\n');
      setState(uuid, d);

      response.write("CrashID=" + uuid + "\n");
    } else {
      dump("*** crashreport.sjs: Malformed request?\n");
      response.setStatusLine(request.httpVersion, 400, "Bad Request");
      response.write("Missing minidump file");
    }
  } else {
    response.setStatusLine(request.httpVersion, 405, "Method not allowed");
    response.write("Can't handle HTTP method " + request.method);
  }
}