Web API for code.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

145 lines
4.3 KiB

  1. import fastify, {FastifyRequest, FastifyServerOptions} from 'fastify';
  2. import {fastifyErrorHandler} from './packages/fastify-compliant-http-errors';
  3. import fastifySendData from './packages/fastify-send-data';
  4. import fastifyServiceSession from './packages/fastify-service-session';
  5. import {Session, SessionService, SessionServiceImpl} from './modules/auth';
  6. import {Uuid} from '@theoryofnekomata/uuid-buffer';
  7. import fastifyHomeRoute from './packages/fastify-home-route';
  8. import {User} from './modules/user';
  9. import {IncomingMessage} from 'http';
  10. import {createDeflate, createGunzip} from 'zlib';
  11. import {PassThrough, Transform} from 'stream';
  12. interface RequestLike extends Pick<FastifyRequest, 'query' | 'headers' | 'body'> {}
  13. declare module 'fastify' {
  14. interface FastifyRequest {
  15. session?: Session & { user: User },
  16. }
  17. }
  18. enum AvailableDecoder {
  19. GZIP = 'gzip',
  20. DEFLATE = 'deflate',
  21. }
  22. type DecoderFunction = () => Transform;
  23. const DECODERS: Record<AvailableDecoder, DecoderFunction> = {
  24. [AvailableDecoder.GZIP]:() => createGunzip(),
  25. [AvailableDecoder.DEFLATE]: () => createDeflate(),
  26. }
  27. const DEFAULT_DECODER: DecoderFunction = () => new PassThrough();
  28. class PackRequestTransformStream extends Transform {
  29. private data: Record<string, unknown> = {
  30. last: null,
  31. commit: null,
  32. ref: null,
  33. }
  34. constructor() {
  35. super();
  36. const headerRegExp = /([0-9a-fA-F]+) ([0-9a-fA-F]+) (.+?)( |00|\u0000)|^(0000)$/gi;
  37. let bufferedData = Buffer.from('');
  38. this.on('data', (chunk) => {
  39. const isHeaderRead = this.data.last && this.data.commit;
  40. if (!isHeaderRead) {
  41. bufferedData = Buffer.concat([bufferedData, chunk]);
  42. const bufferAsString = bufferedData.toString('utf-8');
  43. const bufferHeaderMatch = bufferAsString.match(headerRegExp);
  44. if (bufferHeaderMatch) {
  45. const [
  46. _header,
  47. last,
  48. commit,
  49. ref,
  50. ] = Array.from(bufferHeaderMatch);
  51. this.data = {
  52. last,
  53. commit,
  54. ref,
  55. }
  56. }
  57. }
  58. this.push(chunk);
  59. })
  60. }
  61. }
  62. const receivePackRequestParser = (
  63. request: FastifyRequest,
  64. payload: IncomingMessage,
  65. done: Function,
  66. ) => {
  67. console.log(request.headers);
  68. const encoding = request.headers['content-encoding'];
  69. let theDecoder: DecoderFunction = DEFAULT_DECODER;
  70. if (encoding) {
  71. const { [encoding as AvailableDecoder]: decoder } = DECODERS;
  72. if (!decoder) {
  73. done(new Error(`Unknown encoding: ${encoding}`));
  74. return;
  75. }
  76. theDecoder = decoder;
  77. }
  78. const requestParserStream = new PackRequestTransformStream();
  79. const pipeline = payload
  80. .pipe(theDecoder())
  81. .pipe(requestParserStream)
  82. pipeline.on('end', () => {
  83. done(null, pipeline);
  84. });
  85. };
  86. export const createServer = (opts?: FastifyServerOptions) => {
  87. const server = fastify(opts);
  88. server.setErrorHandler(fastifyErrorHandler)
  89. server.register(fastifyHomeRoute);
  90. server.addContentTypeParser('application/x-git-receive-pack-request', receivePackRequestParser);
  91. server.addContentTypeParser('application/x-git-upload-pack-request', receivePackRequestParser); // TODO
  92. server.register(fastifyServiceSession, {
  93. sessionRequestKey: 'session',
  94. getSession: async (sessionId: string) => {
  95. const sessionService: SessionService = new SessionServiceImpl()
  96. return sessionService.get(Uuid.from(sessionId))
  97. },
  98. isSessionValid: async (sessionId: string) => {
  99. const sessionService: SessionService = new SessionServiceImpl()
  100. return sessionService.isValid(Uuid.from(sessionId))
  101. },
  102. extractSessionId: (request: RequestLike) => {
  103. const sessionIdKey = 'sessionId'
  104. const query = request.query as Record<string, unknown>
  105. const querySessionId = query[sessionIdKey]
  106. if (typeof querySessionId === 'string') {
  107. return querySessionId
  108. }
  109. const { authorization } = request.headers
  110. if (typeof authorization === 'string') {
  111. const [authType, headerSessionId] = authorization.split(' ')
  112. if (authType === 'Bearer') {
  113. return headerSessionId
  114. }
  115. }
  116. const body = request.body as Record<string, unknown> ?? {}
  117. const bodySessionId = body[sessionIdKey]
  118. if (typeof bodySessionId === 'string') {
  119. return bodySessionId
  120. }
  121. return undefined
  122. }
  123. })
  124. server.register(fastifySendData);
  125. return server;
  126. }