1#!/usr/bin/env ruby
  2#
  3# Copyright (C) 2017  Denver Gingerich <denver@ossguy.com>
  4# Copyright (C) 2017  Stephen Paul Weber <singpolyma@singpolyma.net>
  5#
  6# This file is part of sgx-catapult.
  7#
  8# sgx-catapult is free software: you can redistribute it and/or modify it under
  9# the terms of the GNU Affero General Public License as published by the Free
 10# Software Foundation, either version 3 of the License, or (at your option) any
 11# later version.
 12#
 13# sgx-catapult is distributed in the hope that it will be useful, but WITHOUT
 14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 15# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 16# details.
 17#
 18# You should have received a copy of the GNU Affero General Public License along
 19# with sgx-catapult.  If not, see <http://www.gnu.org/licenses/>.
 20
 21$stdout.sync = true
 22
 23puts "Soprani.ca/MMS Proxy for XMPP - Catapult\n"\
 24	"==>> last commit of this version is " + `git rev-parse HEAD` + "\n"
 25
 26require 'em-hiredis'
 27require 'em-http-request'
 28require 'goliath'
 29require 'uri'
 30
 31require_relative 'em_promise'
 32
 33t = Time.now
 34puts "LOG %d.%09d: starting...\n\n" % [t.to_i, t.nsec]
 35
 36EM.next_tick do
 37	REDIS = EM::Hiredis.connect
 38end
 39
 40class WebhookHandler < Goliath::API
 41	def media_request(env, user_id, token, secret, method, media_id)
 42		if ![:get, :head].include?(method)
 43			env.logger.debug 'ERROR: received non-HEAD/-GET request'
 44			return EMPromise.reject(405)
 45		end
 46
 47		EM::HttpRequest.new(
 48			"https://api.catapult.inetwork.com/v1/users/"\
 49			"#{user_id}/media/#{media_id}"
 50		).public_send(
 51			method,
 52			head: {
 53				'Authorization' => [token, secret]
 54			}
 55		).then { |http|
 56			env.logger.debug "API response code to send: " +
 57				http.response_header.status.to_s
 58
 59			case http.response_header.status
 60			when 200
 61				http
 62			else
 63				EMPromise.reject(http.response_header.status)
 64			end
 65		}
 66	end
 67
 68	def response(env)
 69		env.logger.debug 'ENV: ' + env.to_s
 70		env.logger.debug 'path: ' + env['REQUEST_PATH']
 71		env.logger.debug 'method: ' + env['REQUEST_METHOD']
 72		env.logger.debug 'BODY: ' + Rack::Request.new(env).body.read
 73
 74		jid, media_id = env['REQUEST_PATH'].split('/')[-2..-1]
 75		cred_key = "catapult_cred-#{URI.unescape(jid)}"
 76
 77		REDIS.lrange(cred_key, 0, 2).then { |creds|
 78			if creds.length < 3
 79				EMPromise.reject(404)
 80			else
 81				media_request(
 82					env,
 83					*creds,
 84					env['REQUEST_METHOD'].downcase.to_sym,
 85					media_id
 86				)
 87			end
 88		}.then { |http|
 89			clength = http.response_header['content-length']
 90			[200, {'Content-Length' => clength}, http.response]
 91		}.catch { |code|
 92			if code.is_a?(Integer)
 93				EMPromise.reject(code)
 94			else
 95				env.logger.error("ERROR: #{code.inspect}")
 96				EMPromise.reject(500)
 97			end
 98		}.catch { |code|
 99			[
100				code,
101				{'Content-Type' => 'text/plain;charset=utf-8'},
102				case code
103				when 404
104					"not found\n"
105				when 405
106					"only HEAD and GET are allowed\n"
107				else
108					"unexpected error\n"
109				end
110			]
111		}.sync
112	end
113end