Help us understand the problem. What is going on with this article?

netcatでHTTP POSTするときに改行コードでハマった

雰囲気でHTTPを扱っていてハマった話。

環境

  • OS: macOS Sierra
  • Webサーバーの実行環境: Node.js1

netcatでHTTP GETする

HTTPの仕組みを理解するための勉強方法として、netcat2でHTTPプロトコルを手打ちするという方法があります。

server01.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('working!');
});

app.listen(3000);

このようなWebサーバーを立てたとき、curlコマンドでアクセスすると次のようになります。

$ curl localhost:3000
working!

では、これと同じことをnetcat(以降nc)を使ってやってみましょう。

$ nc localhost 3000
GET / HTTP/1.1

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 8
ETag: W/"8-8rFUSOp1Q66VQKob7hAkNyUVaiI"
Date: Wed, 05 Feb 2020 16:01:00 GMT
Connection: keep-alive

working!

GET / HTTP/1.1 の部分をキーボード入力すると、 HTTP/1.1 200 OK で始まる数行の文字列が返され、最後に working! の文字列が表示されました。

という感じで、なんとなくHTTPの雰囲気を味わうことができます。

netcatでHTTP POSTする

ファイルをアップロードできる簡易的なWebサーバーを立てます。

server02.js
const express = require('express');
const path = require('path');
const multer = require('multer');

const upload = multer({ dest: path.resolve('tmp') });
const app = express();

app.get('/form', (req, res) => {
  res.send(`<html>

  <body>
    <form method="POST" action="/upload" enctype="multipart/form-data">
      <input type="file" name="file"><br>
      <input type="submit" value="upload">
    </form>
  </body>

  </html>`);
});

app.get('/download/:fileName', (req, res) => {
  const fileName = req.params.fileName;
  res.download(path.resolve('tmp', fileName));
});

app.post('/upload', upload.single('file'), (req, res) => {
  res.send({ download: '/download/' + req.file.filename });
});

app.listen(3000);

まずは、試しにブラウザからファイルをアップロードしてみます。

image.png

このときの通信内容をWireshark3でキャプチャすると次のようになります。

image.png

なるほど...こんな感じの内容で手打ちすればファイルがPOSTできそうです。
ということで、上の画像の赤色の部分の文字列をコピペして、ncコマンドに突っ込んでみます。

$ nc localhost 3000
POST /upload HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 196
Cache-Control: max-age=0
Origin: http://localhost:3000
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGjg4YppOrQq8zAjm
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Referer: http://localhost:3000/form
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8

------WebKitFormBoundaryGjg4YppOrQq8zAjm
Content-Disposition: form-data; name="file"; filename="hoge.txt"
Content-Type: text/plain

hello, world!

------WebKitFormBoundaryGjg4YppOrQq8zAjm--

(反応が返ってこなかったので何度かReturn連打)




HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 352
Date: Wed, 05 Feb 2020 16:56:40 GMT
Connection: keep-alive

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
~~以下省略~~

コピペした内容を、ncコマンドに突っ込んでみたものの、反応なし。その後何度かReturnキーを押すと Internal Server Error が返りました。

改行コードがCRLFからLFに変わってしまった

ncコマンドに突っ込んだ方は改行コードがCRLFからLFに変わってしまったため、 Content-Length が実際より長くなってしまったようです。

↓ ブラウザからアップロードした時のWiresharkのキャプチャ結果(16進数ダンプ)
image.png

↓ ncコマンド実行時のWiresharkのキャプチャ結果(16進数ダンプ)
image.png

改行が 0d 0a (CRLF) から 0a (LF) に変わっていました...
これだと一行あたり1byte短くなってしまう...

Content-Length を短くして再度実行

image.png

Content-Length を189にして再度ncコマンドに突っ込んでみます。

$ nc localhost 3000
POST /upload HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 189
~中略~
Accept-Language: ja,en-US;q=0.9,en;q=0.8

------WebKitFormBoundaryBR5buZN29ZTV1NcQ
Content-Disposition: form-data; name="file"; filename="hoge.txt"
Content-Type: text/plain

hello, world!

------WebKitFormBoundaryBR5buZN29ZTV1NcQ--
HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 352
Date: Wed, 05 Feb 2020 17:35:27 GMT
Connection: keep-alive

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
~~以下省略~~

今度はすぐに反応が返ってきましたが、Internal Server Error ...
うまくいかない...

CRLFじゃないとだめらしい

RFC2616の3.7.2 Multipart Typesに次のような記述がありました...

https://tools.ietf.org/html/rfc2616#section-3.7.2
The message body is itself a protocol element and MUST therefore use only CRLF to represent line breaks between body-parts.

ということなので、改行をLFからCRLFに置換したものを、ncコマンドに突っ込んでみました。

$ perl -pe 's/\n/\r\n/' << EOS > reqest.txt
POST /upload HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 196
Cache-Control: max-age=0
Origin: http://localhost:3000
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBR5buZN29ZTV1NcQ
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Referer: http://localhost:3000/form
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8

------WebKitFormBoundaryBR5buZN29ZTV1NcQ
Content-Disposition: form-data; name="file"; filename="hoge.txt"
Content-Type: text/plain

hello, world!

------WebKitFormBoundaryBR5buZN29ZTV1NcQ--

EOS
$ cat reqest.txt - | nc localhost 3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 57
ETag: W/"39-ItS2HTg6ed8Zw1CrGzQL3ABlypc"
Date: Wed, 05 Feb 2020 18:38:11 GMT
Connection: keep-alive

{"download":"/download/bd29989fa6ae794857ba429ced286f3f"}
^C
$ curl localhost:3000/download/bd29989fa6ae794857ba429ced286f3f
hello, world!
  • \n -> \r\n の置換はsedだとうまくいかなかったのでperlを使った
  • 一旦ファイルに書き出して cat reqest.txt - をしているのは、パイプの終端に達した時にncコマンドがレスポンスを受け取らずに接続を切ってしまう挙動をするため...これも結構ハマった

ちょっとぎこちないけど、まあ動いたのでよしとしましょう。

※ちなみに、macOSだとこのように問題になりますが、WindowsだとOSの改行コードがCRLFなので何も問題は起きません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした