@@ -23,19 +23,17 @@ import cors from "cors";
2323import http from "http" ;
2424import https from "https" ;
2525import { readFileSync } from "fs" ;
26+ import { parse as parseContentType } from "content-type" ;
2627
2728import { Rendezvous } from "./rendezvous" ;
28- import { maxBytes , ttlSeconds , port } from "./config" ;
29+ import { maxBytes , ttlSeconds , port , trustProxy } from "./config" ;
2930
3031const app = express ( ) ;
31- app . use ( cors ( {
32- allowedHeaders : [ "Content-Type" , "If-Match" , "If-None-Match" ] ,
33- exposedHeaders : [ "ETag" , "Location" , "X-Max-Bytes" ] ,
34- } ) ) ;
3532app . use ( morgan ( "common" ) ) ;
3633app . set ( "env" , "production" ) ;
3734app . set ( "x-powered-by" , false ) ;
3835app . set ( "etag" , false ) ;
36+ app . set ( "trust proxy" , trustProxy ) ;
3937
4038// treat everything as raw
4139app . use ( bodyParser . raw ( {
@@ -51,59 +49,96 @@ const rvs = new NodeCache({
5149 useClones : false ,
5250} ) ;
5351
54- app . post ( "/" , ( req , res ) => {
55- let id : string | undefined ;
56- while ( ! id || id in rvs ) {
57- id = v4 ( ) ;
58- }
59- const rv = new Rendezvous ( id , ttlSeconds , maxBytes , req ) ;
60- rvs . set ( id , rv , rv . ttlSeconds ) ;
52+ app . options ( "/" , cors ( {
53+ origin : "*" ,
54+ methods : [ "GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" ] ,
55+ allowedHeaders : [ "X-Requested-With" , "Content-Type" , "Authorization" ] , // https://spec.matrix.org/v1.10/client-server-api/#web-browser-clients
56+ exposedHeaders : [ "ETag" ] ,
57+ } ) ) ;
6158
62- rv . setHeaders ( res ) ;
59+ function notFound ( res : express . Response ) : express . Response {
60+ return res . status ( 404 ) . json ( { "errcode" : "M_NOT_FOUND" , "error" : "Rendezvous not found" } ) ;
61+ }
6362
64- res . setHeader ( "Location" , id ) ;
65- res . setHeader ( "X-Max-Bytes" , maxBytes ) ;
63+ function withContentType ( req : express . Request , res : express . Response ) {
64+ return ( fn : ( contentType : string ) => express . Response ) : express . Response => {
65+ const contentType = req . get ( "content-type" ) ;
66+ if ( ! contentType ) {
67+ return res . status ( 400 ) . json ( { "errcode" : "M_MISSING_PARAM" , "error" : "Missing Content-Type header" } ) ;
68+ }
69+ try {
70+ parseContentType ( contentType ) ;
71+ } catch ( e ) {
72+ return res . status ( 400 ) . json ( { "errcode" : "M_INVALID_PARAM" , "error" : "Invalid Content-Type header" } ) ;
73+ }
74+ return fn ( contentType ) ;
75+ } ;
76+ }
77+ app . post ( "/" , ( req , res ) => {
78+ withContentType ( req , res ) ( ( contentType ) => {
6679
67- return res . sendStatus ( 201 ) ;
68- } ) ;
80+ let id : string | undefined ;
81+ while ( ! id || id in rvs ) {
82+ id = v4 ( ) ;
83+ }
84+ const rv = new Rendezvous ( id , ttlSeconds , maxBytes , req . body , contentType ) ;
85+ rvs . set ( id , rv , rv . ttlSeconds ) ;
6986
70- app . put ( "/:id" , ( req , res ) => {
71- const { id } = req . params ;
72- const rv = rvs . get < Rendezvous > ( id ) ;
87+ rv . setHeaders ( res ) ;
7388
74- if ( ! rv ) {
75- return res . sendStatus ( 404 ) ;
76- }
89+ const url = `${ req . protocol } ://${ req . hostname } /${ id } ` ;
7790
78- if ( rv . expired ( ) ) {
79- rvs . del ( id ) ;
80- return res . sendStatus ( 404 ) ;
81- }
91+ return res . status ( 201 ) . json ( { url } ) ;
92+ } ) ;
93+ } ) ;
8294
83- const ifMatch = req . headers [ "if-match" ] ;
95+ app . options ( "/:id" , cors ( {
96+ origin : "*" ,
97+ methods : [ "GET" , "PUT" , "DELETE" , "OPTIONS" ] ,
98+ allowedHeaders : [ "If-Match" , "If-None-Match" ] ,
99+ exposedHeaders : [ "ETag" ] ,
100+ } ) ) ;
84101
85- if ( ifMatch && ifMatch !== rv . etag ) {
102+ app . put ( "/:id" , ( req , res ) => {
103+ withContentType ( req , res ) ( ( contentType ) => {
104+ const { id } = req . params ;
105+ const rv = rvs . get < Rendezvous > ( id ) ;
106+
107+ if ( ! rv ) {
108+ return notFound ( res ) ;
109+ }
110+
111+ if ( rv . expired ( ) ) {
112+ rvs . del ( id ) ;
113+ return notFound ( res ) ;
114+ }
115+
116+ const ifMatch = req . headers [ "if-match" ] ;
117+ if ( ! ifMatch ) {
118+ return res . status ( 400 ) . json ( { "errcode" : "M_MISSING_PARAM" , "error" : "Missing If-Match header" } ) ;
119+ } else if ( ifMatch !== rv . etag ) {
120+ rv . setHeaders ( res ) ;
121+ return res . send ( 412 ) . json ( { "errcode" : "M_CONCURRENT_WRITE" , "error" : "Rendezvous has been modified" } ) ;
122+ }
123+
124+ rv . update ( req . body , contentType ) ;
86125 rv . setHeaders ( res ) ;
87- return res . sendStatus ( 412 ) ;
88- }
89126
90- rv . update ( req ) ;
91- rv . setHeaders ( res ) ;
92-
93- return res . sendStatus ( 202 ) ;
127+ return res . sendStatus ( 202 ) ;
128+ } ) ;
94129} ) ;
95130
96131app . get ( "/:id" , ( req , res ) => {
97132 const { id } = req . params ;
98133 const rv = rvs . get < Rendezvous > ( id ) ;
99134
100135 if ( ! rv ) {
101- return res . sendStatus ( 404 ) ;
136+ return notFound ( res ) ;
102137 }
103138
104139 if ( rv . expired ( ) ) {
105140 rvs . del ( id ) ;
106- return res . sendStatus ( 404 ) ;
141+ return notFound ( res ) ;
107142 }
108143
109144 rv . setHeaders ( res ) ;
@@ -118,7 +153,7 @@ app.get("/:id", (req, res) => {
118153app . delete ( "/:id" , ( req , res ) => {
119154 const { id } = req . params ;
120155 if ( ! rvs . has ( id ) ) {
121- return res . sendStatus ( 404 ) ;
156+ return notFound ( res ) ;
122157 }
123158 rvs . del ( id ) ;
124159 return res . sendStatus ( 204 ) ;
@@ -130,11 +165,11 @@ if (process.env.DEV_SSL === "yes") {
130165 cert : readFileSync ( "./devssl/cert.pem" ) ,
131166 } , app ) ;
132167 httpsServer . listen ( port , ( ) => {
133- console . log ( `Starting rendezvous server at https://0.0.0.0:${ port } with self-signed certificate` ) ;
168+ console . log ( `Starting rendezvous server at https://0.0.0.0:${ port } with self-signed certificate${ trustProxy ? " behind trusted proxy" : "" } ` ) ;
134169 } ) ;
135170} else {
136171 const httpServer = http . createServer ( app ) ;
137172 httpServer . listen ( port , ( ) => {
138- console . log ( `Starting rendezvous server at http://0.0.0.0:${ port } ` ) ;
173+ console . log ( `Starting rendezvous server at http://0.0.0.0:${ port } ${ trustProxy ? " behind trusted proxy" : "" } ` ) ;
139174 } ) ;
140175}
0 commit comments