Advertisement
Guest User

2ch API Proxy

a guest
Feb 21st, 2015
756
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 21.16 KB | None | 0 0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Specialized;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Security.Cryptography;
  9. using System.Text;
  10. using System.Text.RegularExpressions;
  11.  
  12. namespace _2chAPIKage
  13. {
  14.     /// <summary>
  15.     /// 2ch DAT→2ch API変換串のサンプル
  16.     /// SessionIDの期限とかしくじった時のエラーコードとかが判らんから適当。3回に1回くらいはうまくいく
  17.     /// このソースの元になったソース
  18.     /// C# と .NET Framework で作る簡単プロキシサーバ
  19.     /// http://d.hatena.ne.jp/wwwcfe/20081228/1230470881
  20.     /// 2ch APIにポストする部分
  21.     /// WebRequest/WebResponseクラスでPOSTメソッドによりデータを送信するには?
  22.     /// http://www.atmarkit.co.jp/fdotnet/dotnettips/318webpost/webpost.html
  23.     /// 差分取得に対応させるための部分
  24.     /// http://rsdn.ru/forum/dotnet/1966333.all
  25.     /// 2chAPIの仕様書
  26.     /// http://codepad.org/9ZfVq5aZ
  27.     /// 認証メソッドはここからベタ移植
  28.     /// http://codepad.org/6M2KhlKR
  29.     /// </summary>
  30.     class HttpServer
  31.     {
  32.         HttpListener listener;
  33.         private const string AppKey = "自分で調べろ";
  34.         private const string HMKey = "自分で調べろ";
  35.  
  36.         public bool IsListening
  37.         {
  38.             get
  39.             {
  40.                 return (listener != null) && listener.IsListening;
  41.             }
  42.         }
  43.  
  44.         public HttpServer()
  45.         {
  46.             listener = null;
  47.         }
  48.  
  49.         public void Start()
  50.         {
  51.             if (listener != null)
  52.                 return;
  53.  
  54.             Debug.WriteLine("Enter HttpServer::Start");
  55.  
  56.             listener = new HttpListener();
  57.             listener.Prefixes.Add(string.Format("http://{0}:{1}/", IPAddress.Loopback, 8081));
  58.             listener.Start();
  59.  
  60.             listener.BeginGetContext(EndGetContext, listener);
  61.             Debug.WriteLine("Exit HttpServer::Start");
  62.         }
  63.  
  64.         public void Stop()
  65.         {
  66.             if (listener == null)
  67.                 return;
  68.  
  69.             Debug.WriteLine("Enter HttpServer::Stop");
  70.             listener.Stop();
  71.             listener.Close();
  72.             listener = null;
  73.             Debug.WriteLine("Exit HttpServer::Stop");
  74.         }
  75.  
  76.         private void EndGetContext(IAsyncResult ar)
  77.         {
  78.             Debug.WriteLine("Enter HttpServer::EndGetContext");
  79.             HttpListener listener = ar.AsyncState as HttpListener;
  80.             if (!listener.IsListening) // 呼び出した listener が Stop されているなら何もしない
  81.             {
  82.                 Debug.WriteLine("Exit HttpServer::EndGetContext listener stopped");
  83.                 return;
  84.             }
  85.  
  86.             HttpListenerContext context = null;
  87.  
  88.  
  89.             try
  90.             {
  91.                 listener.BeginGetContext(EndGetContext, listener);
  92.                 context = listener.EndGetContext(ar);
  93.                 HandleRequest(context);
  94.             }
  95.             catch (Exception ex)
  96.             {
  97.                 Debug.WriteLine("Exception HttpServer::EndGetContext " + ex.Message);
  98.                 if (context != null)
  99.                     context.Response.Abort();
  100.             }
  101.             finally
  102.             {
  103.                 if (context != null)
  104.                     context.Response.Close();
  105.                 Debug.WriteLine("Exit HttpServer::EndGetContext");
  106.             }
  107.         }
  108.  
  109.         private HttpWebRequest CreateRequest(HttpListenerContext context, bool keepalive)
  110.         {
  111.             // HttpListenerRequest と同じ HttpWebRequest を作る。
  112.             HttpListenerRequest req = context.Request;
  113.             HttpWebRequest webRequest = WebRequest.Create(req.RawUrl) as HttpWebRequest;
  114.             webRequest.Method = req.HttpMethod;
  115.             webRequest.ProtocolVersion = HttpVersion.Version11;
  116.  
  117.             // 接続してきたクライアントが切断しても、ほかのクライアントでこの WebRequest の接続を再利用できるはずなので常に true でもいいか?
  118.             webRequest.KeepAlive = keepalive;
  119.  
  120.             // HttpWebRequest の制限がきついのでヘッダごとに対応
  121.             for (int i = 0; i < req.Headers.Count; i++)
  122.             {
  123.                 string name = req.Headers.GetKey(i).ToLower();
  124.                 string value = req.Headers.Get(i).ToLower();
  125.  
  126.                 switch (name)
  127.                 {
  128.                     case "host":
  129.                         break; // WebRequest.Create で適切に設定されているはず あとで確認
  130.                     case "connection":
  131.                     case "proxy-connection":
  132.                         webRequest.KeepAlive = keepalive; // TODO: keepalive の取得はここで行う。
  133.                         break;
  134.                     case "referer":
  135.                         webRequest.Referer = value;
  136.                         break;
  137.                     case "user-agent":
  138.                         webRequest.UserAgent = value;
  139.                         break;
  140.                     case "accept":
  141.                         webRequest.Accept = value;
  142.                         break;
  143.                     case "content-length":
  144.                         webRequest.ContentLength = req.ContentLength64;
  145.                         break;
  146.                     case "content-type":
  147.                         webRequest.ContentType = value;
  148.                         break;
  149.                     case "if-modified-since":
  150.                         webRequest.IfModifiedSince = DateTime.Parse(value);
  151.                         break;
  152.                     case "range":
  153.                     {
  154.                         string rangesStr = value.Split('=')[1];
  155.                         foreach (string rangeStr in rangesStr.Split(','))
  156.                         {
  157.                             string[] ranges = rangeStr.Split(new char[] { '-' }, StringSplitOptions.None);
  158.                             if (ranges[0] == "")
  159.                             {
  160.                                 if (ranges[1] == "")
  161.                                 {
  162.  
  163.                                 }
  164.                                 else
  165.                                 {
  166.                                     webRequest.AddRange(-int.Parse(ranges[1]));
  167.                                 }
  168.                             }
  169.                             else
  170.                             {
  171.                                 if (ranges[1] == "")
  172.                                 {
  173.                                     webRequest.AddRange(int.Parse(ranges[0]));
  174.                                 }
  175.                                 else
  176.                                 {
  177.                                     webRequest.AddRange(int.Parse(ranges[0]), int.Parse(ranges[1]));
  178.                                 }
  179.                             }
  180.                         }
  181.                     }
  182.                         break;
  183.                     default:
  184.                         try
  185.                         {
  186.                             // その他。上以外にも個別に対応しなければならないものがあるが面倒なのでパス
  187.                             webRequest.Headers.Set(name, value);
  188.                         }
  189.                         catch
  190.                         {
  191.                             Debug.WriteLine("Exception HttpServer::CreateRequest header=" + name);
  192.                         }
  193.                         break;
  194.                 }
  195.             }
  196.  
  197.             return webRequest;
  198.         }
  199.  
  200.         public string Authenticate2ChApi()
  201.         {
  202.             WebClient wc = new WebClient();
  203.             string CT = "1234567890";
  204.             string message = AppKey + CT;
  205.             string url = "https://api.2ch.net/v1/auth/";
  206.             HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(HMKey));
  207.             string HB = BitConverter.ToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(message))).ToLower().Replace("-", "");
  208.             hmac.Clear();
  209.  
  210.             NameValueCollection ps = new NameValueCollection();
  211.             ps.Add("ID", "");
  212.             ps.Add("PW", "");
  213.             ps.Add("KY", AppKey);
  214.             ps.Add("CT", CT);
  215.             ps.Add("HB", HB);
  216.             wc.Headers.Add(HttpRequestHeader.UserAgent, "Monazilla/1.3");
  217.             wc.Headers.Add("X-2ch-UA", "JaneStyle/3.80");
  218.             byte[] resData = wc.UploadValues(url, ps);
  219.             wc.Dispose();
  220.  
  221.             return Encoding.UTF8.GetString(resData).Split(':')[1];
  222.         }
  223.  
  224.         private HttpWebRequest Create2chRequest(HttpListenerContext context, string serverName, string boardName, string threadId,string sid, bool keepalive)
  225.         {
  226.             // 2ch API 専用の HttpWebRequest を作る。
  227.             HttpListenerRequest req = context.Request;
  228.  
  229.             // POSTする内容の作成
  230.             string url = "https://api.2ch.net/v1/" + serverName + "/" + boardName + "/" + threadId;
  231.             string message = "/v1/" + serverName + "/" + boardName + "/" + threadId + sid + AppKey;
  232.  
  233.             HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(HMKey));
  234.             string hobo = BitConverter.ToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(message))).ToLower().Replace("-", "");
  235.             hmac.Clear();
  236.  
  237.             HttpWebRequest webRequest = WebRequest.Create(url) as HttpWebRequest;
  238.  
  239.             Hashtable ht = new Hashtable();
  240.  
  241.             ht["sid"] = sid;
  242.             ht["hobo"] = hobo;
  243.             ht["appkey"] = AppKey;
  244.  
  245.             string param = ht.Keys.Cast<string>().Aggregate("", (current, k) => current + String.Format("{0}={1}&", k, ht[k]));
  246.             byte[] data = Encoding.ASCII.GetBytes(param);
  247.  
  248.             //ヘッダを設定
  249.             webRequest.Method = "POST";
  250.             webRequest.UserAgent = "Mozilla/3.0 (compatible; JaneStyle/3.80..)";
  251.             webRequest.ContentType = "application/x-www-form-urlencoded";
  252.             webRequest.ProtocolVersion = HttpVersion.Version11;
  253.             webRequest.ContentLength = data.Length;
  254.  
  255.             // 接続してきたクライアントが切断しても、ほかのクライアントでこの WebRequest の接続を再利用できるはずなので常に true でもいいか?
  256.             webRequest.KeepAlive = keepalive;
  257.  
  258.             // 2ch専用ブラウザ側から受取ったヘッダを追加
  259.             for (int i = 0; i < req.Headers.Count; i++)
  260.             {
  261.                 string name = req.Headers.GetKey(i).ToLower();
  262.                 string value = req.Headers.Get(i).ToLower();
  263.  
  264.                 switch (name)
  265.                 {
  266.                     case "connection":
  267.                     case "proxy-connection":
  268.                         webRequest.KeepAlive = keepalive; // TODO: keepalive の取得はここで行う。
  269.                         break;
  270.                     case "accept":
  271.                         webRequest.Accept = value;
  272.                         break;
  273.                     case "accept-encoding":
  274.                         webRequest.Headers.Add("Accept-Encoding", value);
  275.                         break;
  276.                     case "etag":
  277.                         webRequest.Headers.Add("ETag", value);
  278.                         break;
  279.                     case "if-modified-since":
  280.                         webRequest.IfModifiedSince = DateTime.Parse(value);
  281.                         break;
  282.                     case "range":
  283.                         {
  284.                             string rangesStr = value.Split('=')[1];
  285.                             foreach (string rangeStr in rangesStr.Split(','))
  286.                             {
  287.                                 string[] ranges = rangeStr.Split(new char[] { '-' }, StringSplitOptions.None);
  288.                                 if (ranges[0] == "")
  289.                                 {
  290.                                     if (ranges[1] == "")
  291.                                     {
  292.  
  293.                                     }
  294.                                     else
  295.                                     {
  296.                                         webRequest.AddRange(-int.Parse(ranges[1]));
  297.                                     }
  298.                                 }
  299.                                 else
  300.                                 {
  301.                                     if (ranges[1] == "")
  302.                                     {
  303.                                         webRequest.AddRange(int.Parse(ranges[0]));
  304.                                     }
  305.                                     else
  306.                                     {
  307.                                         webRequest.AddRange(int.Parse(ranges[0]), int.Parse(ranges[1]));
  308.                                     }
  309.                                 }
  310.                             }
  311.                         }
  312.                         break;
  313.                 }
  314.             }
  315.  
  316.             //POSTする内容を書き込み
  317.             Stream webRequestStream = webRequest.GetRequestStream();
  318.             webRequestStream.Write(data, 0, data.Length);
  319.             webRequestStream.Close();
  320.  
  321.             return webRequest;
  322.         }
  323.  
  324.  
  325.         private void HandleRequest(HttpListenerContext context)
  326.         {
  327.             // どういう処理をさせるのかを決める。
  328.             HttpListenerRequest req = context.Request;
  329.             HttpListenerResponse res = context.Response;
  330.  
  331.             // どこから接続されたかと、加工されていないアドレス
  332.             Debug.WriteLine("Info HttpServer::HandleRequest " + string.Format("UserHost={0}: Request={1}", req.UserHostAddress, req.RawUrl));
  333.  
  334.             bool keepalive = req.KeepAlive; // 常に false らしい。バグらしい。
  335.             if (!string.IsNullOrEmpty(req.Headers["Connection"]) && req.Headers["Connection"].IndexOf("keep-alive", StringComparison.InvariantCultureIgnoreCase) >= 0 ||
  336.                 !string.IsNullOrEmpty(req.Headers["Proxy-Connection"]) && req.Headers["Proxy-Connection"].IndexOf("keep-alive", StringComparison.InvariantCultureIgnoreCase) >= 0)
  337.                 keepalive = true; // バグ対策?
  338.  
  339.             if (req.RawUrl.StartsWith("/") || req.RawUrl.StartsWith("http://local.ptron/"))
  340.                 ProcessLocalRequest(context, keepalive);
  341.             else // プロクシサーバとしての振る舞い
  342.                 ProcessProxyRequest(context, keepalive);
  343.         }
  344.  
  345.         private void ProcessProxyRequest(HttpListenerContext context, bool keepalive)
  346.         {
  347.             // プロキシサーバとしての動作。
  348.             HttpListenerRequest req = context.Request;
  349.             HttpListenerResponse res = context.Response;
  350.  
  351.             // もし2chのDATのURLだったらRequestをAPI用に加工する。
  352.             Regex regex = new Regex(@"http://(\w+)\.2ch\.net:80/(\w+)/dat/(\d+)\.dat");
  353.             Match match = regex.Match(req.RawUrl);
  354.             HttpWebRequest webRequest = null;
  355.             if (match.Success)
  356.             {
  357.                 string sid = "";
  358.                 try
  359.                 {
  360.                     Debug.WriteLine("Info HttpServer::ProcessProxyRequest Authenticate2ChApi Start");
  361.                     sid = Authenticate2ChApi();
  362.                     Debug.WriteLine("Info HttpServer::ProcessProxyRequest 2ch API sid :"+sid);
  363.                 }
  364.                 catch (WebException exp)
  365.                 {
  366.                     SendResponse(context, 502, "Bad Gateway",keepalive, null);
  367.                     return;
  368.                 }
  369.                 webRequest = Create2chRequest(context, match.Groups[1].Value, match.Groups[2].Value, match.Groups[3].Value, sid, keepalive);
  370.             }
  371.             else
  372.             {
  373.                 webRequest = CreateRequest(context, keepalive);
  374.             }
  375.  
  376.             // ボディあったら送受信
  377.             if (req.HasEntityBody) // リクエストのボディがある (POST とか)
  378.                 Relay(req.InputStream, webRequest.GetRequestStream());
  379.  
  380.             if (webRequest == null)
  381.             {SendResponse(context, 502, "Bad Gateway", keepalive, null);
  382.                 return;}
  383.  
  384.             // レスポンス取得
  385.             HttpWebResponse webResponse = null;
  386.             try
  387.             {
  388.                 webResponse = webRequest.GetResponse() as HttpWebResponse;
  389.             }
  390.             catch (WebException e)
  391.             {
  392.                 webResponse = e.Response as HttpWebResponse; // レスポンスがあればとる。304 とかの場合。なければ null になる。
  393.                 Debug.WriteLine("Exception HttpServer::ProcessProxyRequest " + e.Message);
  394.             }
  395.  
  396.             // だめだった時の処理。てきとう
  397.             if (webResponse == null)
  398.             {
  399.                 SendResponse(context, 502, "Bad Gateway", keepalive, null);
  400.                 return;
  401.             }
  402.  
  403.             // ブラウザへ返すレスポンスの設定。あるていど。
  404.             res.ProtocolVersion = HttpVersion.Version11; // 常に HTTP/1.1 としておく
  405.             res.StatusCode = (int)webResponse.StatusCode;
  406.             res.StatusDescription = webResponse.StatusDescription;
  407.  
  408.             res.KeepAlive = keepalive;
  409.  
  410.             for (int i = 0; i < webResponse.Headers.Count; i++)
  411.             {
  412.                 string name = webResponse.Headers.GetKey(i).ToLower();
  413.                 string value = webResponse.Headers.Get(i).ToLower();
  414.  
  415.                 switch (name)
  416.                 {
  417.                     case "content-length":
  418.                         res.ContentLength64 = webResponse.ContentLength;
  419.                         break;
  420.                     case "keep-alive": // どうやって設定しようか...
  421.                         Debug.WriteLine("Info HttpServer::ProcessProxyRequest keep-alive: " + value);
  422.                         break;
  423.                     case "transfer-encoding":
  424.                         res.SendChunked = value.IndexOf("chunked") >= 0 ? true : false;
  425.                         break;
  426.                     default:
  427.                         try
  428.                         {
  429.                             res.Headers.Set(name, value);
  430.                         }
  431.                         catch
  432.                         {
  433.                             Debug.WriteLine("Exception HttpServer::ProcessProxyRequest header=" + name);
  434.                         }
  435.                         break;
  436.                 }
  437.             }
  438.  
  439.             Relay(webResponse.GetResponseStream(), res.OutputStream);
  440.  
  441.             webResponse.Close();
  442.         }
  443.  
  444.         private void ProcessLocalRequest(HttpListenerContext context, bool keepalive)
  445.         {
  446.             HttpListenerRequest req = context.Request;
  447.  
  448.             // 通常の HTTP サーバとしての振る舞い。または local.ptron へのアクセス。
  449.             if (req.RawUrl.Equals("/") || req.RawUrl.Equals("http://local.ptron/"))
  450.                 SendResponse(context, 200, "OK", keepalive, Encoding.Default.GetBytes("Hello World"));
  451.             else // favicon とか取りに来るので。
  452.                 SendResponse(context, 404, "Not Found", keepalive, Encoding.Default.GetBytes("404 not found"));
  453.         }
  454.  
  455.         private void Relay(Stream input, Stream output)
  456.         {
  457.             // Stream から読めなくなるまで送受信。
  458.             byte[] buffer = new byte[4096];
  459.             while (true)
  460.             {
  461.                 int bytesRead = input.Read(buffer, 0, buffer.Length);
  462.                 if (bytesRead == 0)
  463.                     break;
  464.                 output.Write(buffer, 0, bytesRead);
  465.             }
  466.  
  467.             input.Close();
  468.             output.Close();
  469.         }
  470.  
  471.         private void SendResponse(HttpListenerContext context, int code, string description, bool keepalive, byte[] body)
  472.         {
  473.             context.Response.StatusCode = code;
  474.             context.Response.StatusDescription = description;
  475.             context.Response.ProtocolVersion = HttpVersion.Version11;
  476.             context.Response.KeepAlive = keepalive;
  477.             if (body != null)
  478.             {
  479.                 context.Response.ContentType = "text/plain";
  480.                 context.Response.ContentLength64 = body.Length;
  481.  
  482.                 context.Response.OutputStream.Write(body, 0, body.Length);
  483.                 context.Response.OutputStream.Close();
  484.             }
  485.             else
  486.             {
  487.                 context.Response.ContentLength64 = 0;
  488.             }
  489.         }
  490.  
  491.         private void SendFile(HttpListenerContext context, int code, string description, bool keepalive, byte[] body, string contentType)
  492.         {
  493.             context.Response.StatusCode = code;
  494.             context.Response.StatusDescription = description;
  495.             context.Response.ProtocolVersion = HttpVersion.Version11;
  496.             context.Response.KeepAlive = keepalive;
  497.             if (body != null)
  498.             {
  499.                 context.Response.ContentType = contentType;
  500.                 context.Response.ContentLength64 = body.Length;
  501.  
  502.                 context.Response.OutputStream.Write(body, 0, body.Length);
  503.                 context.Response.OutputStream.Close();
  504.             }
  505.             else
  506.             {
  507.                 context.Response.ContentLength64 = 0;
  508.             }
  509.         }
  510.     }
  511. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement