diff --git a/packages/opentelemetry-plugin-http/package.json b/packages/opentelemetry-plugin-http/package.json index 9a7819670c5..a51ac99f253 100644 --- a/packages/opentelemetry-plugin-http/package.json +++ b/packages/opentelemetry-plugin-http/package.json @@ -6,7 +6,7 @@ "types": "build/src/index.d.ts", "repository": "open-telemetry/opentelemetry-js", "scripts": { - "test": "nyc ts-mocha -p tsconfig.json test/**/*.ts", + "test": "nyc ts-mocha -p tsconfig.json test/**/*/*.test.ts", "tdd": "yarn test -- --watch-extensions ts --watch", "clean": "rimraf build/*", "check": "gts check", @@ -38,12 +38,29 @@ "access": "public" }, "devDependencies": { + "@types/got": "^9.6.6", "@types/mocha": "^5.2.7", + "@types/nock": "^10.0.3", "@types/node": "^12.6.9", + "@types/request-promise-native": "^1.0.16", + "@types/semver": "^6.0.1", + "@types/shimmer": "^1.0.1", + "@types/sinon": "^7.0.13", + "@types/superagent": "^4.1.3", + "@opentelemetry/scope-async-hooks": "^0.0.1", + "@opentelemetry/scope-base": "^0.0.1", + "@opentelemetry/basic-tracer": "^0.0.1", + "axios": "^0.19.0", + "got": "^9.6.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "superagent": "5.1.0", "codecov": "^3.5.0", - "gts": "^1.1.0", + "gts": "^1.0.0", "mocha": "^6.2.0", + "nock": "^10.0.6", "nyc": "^14.1.1", + "sinon": "^7.3.2", "ts-mocha": "^6.0.0", "ts-node": "^8.3.0", "typescript": "^3.5.3" @@ -51,6 +68,8 @@ "dependencies": { "@opentelemetry/core": "^0.0.1", "@opentelemetry/node-tracer": "^0.0.1", - "@opentelemetry/types": "^0.0.1" + "@opentelemetry/types": "^0.0.1", + "semver": "^6.3.0", + "shimmer": "^1.2.1" } } diff --git a/packages/opentelemetry-plugin-http/src/enums/AttributeNames.ts b/packages/opentelemetry-plugin-http/src/enums/AttributeNames.ts new file mode 100644 index 00000000000..277dbf1776a --- /dev/null +++ b/packages/opentelemetry-plugin-http/src/enums/AttributeNames.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Attributes Names according to Opencensus HTTP Specs since there is no specific OpenTelemetry Attributes + * https://github.com/open-telemetry/opentelemetry-specification/blob/master/work_in_progress/opencensus/HTTP.md#attributes + */ +export enum AttributeNames { + HTTP_HOSTNAME = 'http.hostname', + COMPONENT = 'component', + HTTP_METHOD = 'http.method', + HTTP_PATH = 'http.path', + HTTP_ROUTE = 'http.route', + HTTP_URL = 'http.url', + HTTP_STATUS_CODE = 'http.status_code', + HTTP_STATUS_TEXT = 'http.status_text', + // NOT ON OFFICIAL SPEC + HTTP_ERROR_NAME = 'http.error_name', + HTTP_ERROR_MESSAGE = 'http.error_message', + HTTP_USER_AGENT = 'http.user_agent', +} diff --git a/packages/opentelemetry-plugin-http/src/enums/Format.ts b/packages/opentelemetry-plugin-http/src/enums/Format.ts new file mode 100644 index 00000000000..66826b5da67 --- /dev/null +++ b/packages/opentelemetry-plugin-http/src/enums/Format.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum Format { + HTTP = 'HttpTraceContext', +} diff --git a/packages/opentelemetry-plugin-http/src/http.ts b/packages/opentelemetry-plugin-http/src/http.ts index e69de29bb2d..2a828e26921 100644 --- a/packages/opentelemetry-plugin-http/src/http.ts +++ b/packages/opentelemetry-plugin-http/src/http.ts @@ -0,0 +1,393 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BasePlugin, isValid } from '@opentelemetry/core'; +import { Span, SpanKind, SpanOptions, Attributes } from '@opentelemetry/types'; +import { + ClientRequest, + IncomingMessage, + request, + RequestOptions, + ServerResponse, +} from 'http'; +import * as semver from 'semver'; +import * as shimmer from 'shimmer'; +import * as url from 'url'; +import { + HttpPluginConfig, + Http, + Func, + ResponseEndArgs, + ParsedRequestOptions, + HttpRequestArgs, +} from './types'; +import { Format } from './enums/Format'; +import { AttributeNames } from './enums/AttributeNames'; +import { Utils } from './utils'; + +/** + * Http instrumentation plugin for Opentelemetry + */ +export class HttpPlugin extends BasePlugin { + static readonly component = 'http'; + options!: HttpPluginConfig; + + constructor(readonly moduleName: string, readonly version: string) { + super(); + // TODO: remove this once options will be passed + // see https://github.com/open-telemetry/opentelemetry-js/issues/210 + this.options = {}; + } + + /** Patches HTTP incoming and outcoming request functions. */ + protected patch() { + this._logger.debug( + 'applying patch to %s@%s', + this.moduleName, + this.version + ); + + shimmer.wrap( + this._moduleExports, + 'request', + this._getPatchOutgoingRequestFunction() + ); + + // In Node 8-10, http.get calls a private request method, therefore we patch it + // here too. + if (semver.satisfies(this.version, '>=8.0.0')) { + shimmer.wrap( + this._moduleExports, + 'get', + this._getPatchOutgoingGetFunction() + ); + } + + if ( + this._moduleExports && + this._moduleExports.Server && + this._moduleExports.Server.prototype + ) { + shimmer.wrap( + this._moduleExports.Server.prototype, + 'emit', + this._getPatchIncomingRequestFunction() + ); + } else { + this._logger.error( + 'Could not apply patch to %s.emit. Interface is not as expected.', + this.moduleName + ); + } + + return this._moduleExports; + } + + /** Unpatches all HTTP patched function. */ + protected unpatch(): void { + shimmer.unwrap(this._moduleExports, 'request'); + if (semver.satisfies(this.version, '>=8.0.0')) { + shimmer.unwrap(this._moduleExports, 'get'); + } + if ( + this._moduleExports && + this._moduleExports.Server && + this._moduleExports.Server.prototype + ) { + shimmer.unwrap(this._moduleExports.Server.prototype, 'emit'); + } + } + + /** + * Creates spans for incoming requests, restoring spans' context if applied. + */ + protected _getPatchIncomingRequestFunction() { + return (original: (event: string, ...args: unknown[]) => boolean) => { + return this._incomingRequestFunction(original); + }; + } + + /** + * Creates spans for outgoing requests, sending spans' context for distributed + * tracing. + */ + protected _getPatchOutgoingRequestFunction() { + return (original: Func): Func => { + return this._outgoingRequestFunction(original); + }; + } + + protected _getPatchOutgoingGetFunction() { + return (original: Func): Func => { + // Re-implement http.get. This needs to be done (instead of using + // getPatchOutgoingRequestFunction to patch it) because we need to + // set the trace context header before the returned ClientRequest is + // ended. The Node.js docs state that the only differences between + // request and get are that (1) get defaults to the HTTP GET method and + // (2) the returned request object is ended immediately. The former is + // already true (at least in supported Node versions up to v10), so we + // simply follow the latter. Ref: + // https://nodejs.org/dist/latest/docs/api/http.html#http_http_get_options_callback + // https://github.com/googleapis/cloud-trace-nodejs/blob/master/src/plugins/plugin-http.ts#L198 + return function outgoingGetRequest< + T extends RequestOptions | string | URL + >(options: T, ...args: HttpRequestArgs) { + const req = request(options, ...args); + req.end(); + return req as ClientRequest; + }; + }; + } + + /** + * Injects span's context to header for distributed tracing and finishes the + * span when the response is finished. + * @param request The original request object. + * @param options The arguments to the original function. + * @param span representing the current operation + */ + private _getMakeRequestTraceFunction( + request: ClientRequest, + options: ParsedRequestOptions, + span: Span + ): Func { + return (): ClientRequest => { + this._logger.debug('makeRequestTrace by injecting context into header'); + + request.on( + 'response', + (response: IncomingMessage & { req?: { method?: string } }) => { + this._tracer.bind(response); + this._logger.debug('outgoingRequest on response()'); + response.on('end', () => { + this._logger.debug('outgoingRequest on end()'); + + const method = + response.req && response.req.method + ? response.req.method.toUpperCase() + : 'GET'; + const headers = options.headers; + const userAgent = headers ? headers['user-agent'] : null; + + const host = options.hostname || options.host || 'localhost'; + + const attributes: Attributes = { + [AttributeNames.HTTP_URL]: `${options.protocol}//${options.hostname}${options.path}`, + [AttributeNames.HTTP_HOSTNAME]: host, + [AttributeNames.HTTP_METHOD]: method, + [AttributeNames.HTTP_PATH]: options.path || '/', + }; + + if (userAgent) { + attributes[AttributeNames.HTTP_USER_AGENT] = userAgent; + } + + if (response.statusCode) { + attributes[AttributeNames.HTTP_STATUS_CODE] = response.statusCode; + attributes[AttributeNames.HTTP_STATUS_TEXT] = + response.statusMessage; + span.setStatus(Utils.parseResponseStatus(response.statusCode)); + } + + span.setAttributes(attributes); + + if (this.options.applyCustomAttributesOnSpan) { + this.options.applyCustomAttributesOnSpan(span, request, response); + } + + span.end(); + }); + Utils.setSpanOnError(span, response); + } + ); + + Utils.setSpanOnError(span, request); + + this._logger.debug('makeRequestTrace return request'); + return request; + }; + } + + private _incomingRequestFunction( + original: (event: string, ...args: unknown[]) => boolean + ) { + const plugin = this; + return function incomingRequest( + this: {}, + event: string, + ...args: unknown[] + ): boolean { + // Only traces request events + if (event !== 'request') { + return original.apply(this, [event, ...args]); + } + + const request = args[0] as IncomingMessage; + const response = args[1] as ServerResponse; + const pathname = request.url + ? url.parse(request.url).pathname || '/' + : '/'; + const method = request.method || 'GET'; + + plugin._logger.debug('%s plugin incomingRequest', plugin.moduleName); + + if (Utils.isIgnored(pathname, plugin.options.ignoreIncomingPaths)) { + return original.apply(this, [event, ...args]); + } + + const propagation = plugin._tracer.getHttpTextFormat(); + const headers = request.headers; + const spanOptions: SpanOptions = { + kind: SpanKind.SERVER, + }; + + const spanContext = propagation.extract(Format.HTTP, headers); + if (spanContext && isValid(spanContext)) { + spanOptions.parent = spanContext; + } + + const span = plugin._startHttpSpan(`${method} ${pathname}`, spanOptions); + + return plugin._tracer.withSpan(span, () => { + plugin._tracer.bind(request); + plugin._tracer.bind(response); + + // Wraps end (inspired by: + // https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/blob/master/src/plugins/plugin-connect.ts#L75) + const originalEnd = response.end; + response.end = function( + this: ServerResponse, + ...args: ResponseEndArgs + ) { + response.end = originalEnd; + // Cannot pass args of type ResponseEndArgs, + // tslint complains "Expected 1-2 arguments, but got 1 or more.", it does not make sense to me + // tslint:disable-next-line:no-any + const returned = response.end.apply(this, arguments as any); + const requestUrl = request.url ? url.parse(request.url) : null; + const hostname = headers.host + ? headers.host.replace(/^(.*)(\:[0-9]{1,5})/, '$1') + : 'localhost'; + const userAgent = headers['user-agent']; + + const attributes: Attributes = { + [AttributeNames.HTTP_URL]: Utils.getUrlFromIncomingRequest( + requestUrl, + headers + ), + [AttributeNames.HTTP_HOSTNAME]: hostname, + [AttributeNames.HTTP_METHOD]: method, + [AttributeNames.HTTP_STATUS_CODE]: response.statusCode, + [AttributeNames.HTTP_STATUS_TEXT]: response.statusMessage, + }; + + if (requestUrl) { + attributes[AttributeNames.HTTP_PATH] = requestUrl.path || '/'; + attributes[AttributeNames.HTTP_ROUTE] = requestUrl.pathname || '/'; + } + + if (userAgent) { + attributes[AttributeNames.HTTP_USER_AGENT] = userAgent; + } + + span + .setAttributes(attributes) + .setStatus(Utils.parseResponseStatus(response.statusCode)); + + if (plugin.options.applyCustomAttributesOnSpan) { + plugin.options.applyCustomAttributesOnSpan(span, request, response); + } + + span.end(); + return returned; + }; + return original.apply(this, [event, ...args]); + }); + }; + } + + private _outgoingRequestFunction( + original: Func + ): Func { + const plugin = this; + return function outgoingRequest( + this: {}, + options: RequestOptions | string, + ...args: unknown[] + ): ClientRequest { + if (!options) { + return original.apply(this, [options, ...args]); + } + + const { origin, pathname, method, optionsParsed } = Utils.getRequestInfo( + options, + typeof args[0] === 'object' && typeof options === 'string' + ? (args.shift() as RequestOptions) + : undefined + ); + options = optionsParsed; + + if ( + Utils.isIgnored(origin + pathname, plugin.options.ignoreOutgoingUrls) + ) { + return original.apply(this, [options, ...args]); + } + + const currentSpan = plugin._tracer.getCurrentSpan(); + const operationName = `${method} ${pathname}`; + const spanOptions: SpanOptions = { + kind: SpanKind.CLIENT, + parent: currentSpan ? currentSpan : undefined, + }; + + const span = plugin._startHttpSpan(operationName, spanOptions); + plugin._tracer + .getHttpTextFormat() + .inject(span.context(), Format.HTTP, options.headers); + + const request: ClientRequest = original.apply(this, [options, ...args]); + + plugin._logger.debug('%s plugin outgoingRequest', plugin.moduleName); + plugin._tracer.bind(request); + + // Checks if this outgoing request is part of an operation by checking + // if there is a current span, if so, we create a child span. In + // case there is no active span, this means that the outgoing request is + // the first operation, therefore we create a span and call withSpan method. + if (!currentSpan) { + plugin._logger.debug('outgoingRequest starting a span without context'); + return plugin._tracer.withSpan( + span, + plugin._getMakeRequestTraceFunction(request, options, span) + ); + } else { + plugin._logger.debug('outgoingRequest starting a child span'); + return plugin._getMakeRequestTraceFunction(request, options, span)(); + } + }; + } + + private _startHttpSpan(name: string, options: SpanOptions) { + return this._tracer + .startSpan(name, options) + .setAttribute(AttributeNames.COMPONENT, HttpPlugin.component); + } +} + +export const plugin = new HttpPlugin( + HttpPlugin.component, + process.versions.node +); diff --git a/packages/opentelemetry-plugin-http/src/index.ts b/packages/opentelemetry-plugin-http/src/index.ts new file mode 100644 index 00000000000..45181e584f1 --- /dev/null +++ b/packages/opentelemetry-plugin-http/src/index.ts @@ -0,0 +1,21 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './http'; +export * from './types'; +export * from './utils'; +export * from './enums/AttributeNames'; +export * from './enums/Format'; diff --git a/packages/opentelemetry-plugin-http/src/types.ts b/packages/opentelemetry-plugin-http/src/types.ts new file mode 100644 index 00000000000..e01839e142b --- /dev/null +++ b/packages/opentelemetry-plugin-http/src/types.ts @@ -0,0 +1,64 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Span } from '@opentelemetry/types'; +import * as url from 'url'; +import { + ClientRequest, + IncomingMessage, + ServerResponse, + request, + get, +} from 'http'; +import * as http from 'http'; + +export type IgnoreMatcher = string | RegExp | ((url: string) => boolean); +export type HttpCallback = (res: IncomingMessage) => void; +export type RequestFunction = typeof request; +export type GetFunction = typeof get; + +export type HttpCallbackOptional = HttpCallback | undefined; + +// from node 10+ +export type RequestSignature = [http.RequestOptions, HttpCallbackOptional] & + HttpCallback; + +export type HttpRequestArgs = Array; + +export type ParsedRequestOptions = + | http.RequestOptions & Partial + | http.RequestOptions; +export type Http = typeof http; +/* tslint:disable-next-line:no-any */ +export type Func = (...args: any[]) => T; +export type ResponseEndArgs = + | [((() => void) | undefined)?] + | [unknown, ((() => void) | undefined)?] + | [unknown, string, ((() => void) | undefined)?]; + +export interface HttpCustomAttributeFunction { + ( + span: Span, + request: ClientRequest | IncomingMessage, + response: IncomingMessage | ServerResponse + ): void; +} + +export interface HttpPluginConfig { + ignoreIncomingPaths?: IgnoreMatcher[]; + ignoreOutgoingUrls?: IgnoreMatcher[]; + applyCustomAttributesOnSpan?: HttpCustomAttributeFunction; +} diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts new file mode 100644 index 00000000000..b48e186d71f --- /dev/null +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -0,0 +1,210 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Status, CanonicalCode, Span } from '@opentelemetry/types'; +import { + RequestOptions, + IncomingMessage, + ClientRequest, + IncomingHttpHeaders, +} from 'http'; +import { IgnoreMatcher } from './types'; +import { AttributeNames } from './enums/AttributeNames'; +import * as url from 'url'; + +/** + * Utility class + */ +export class Utils { + /** + * return an absolute url + */ + static getUrlFromIncomingRequest( + requestUrl: url.UrlWithStringQuery | null, + headers: IncomingHttpHeaders + ): string { + if (!requestUrl) { + return `http://${headers.host || 'localhost'}/`; + } + + return requestUrl.href && requestUrl.href.startsWith('http') + ? `${requestUrl.protocol}//${requestUrl.hostname}${requestUrl.path}` + : `${requestUrl.protocol || 'http:'}//${headers.host || + 'localhost'}${requestUrl.path || '/'}`; + } + /** + * Parse status code from HTTP response. + */ + static parseResponseStatus(statusCode: number): Omit { + if (statusCode < 200 || statusCode > 504) { + return { code: CanonicalCode.UNKNOWN }; + } else if (statusCode >= 200 && statusCode < 400) { + return { code: CanonicalCode.OK }; + } else { + switch (statusCode) { + case 400: + return { code: CanonicalCode.INVALID_ARGUMENT }; + case 504: + return { code: CanonicalCode.DEADLINE_EXCEEDED }; + case 404: + return { code: CanonicalCode.NOT_FOUND }; + case 403: + return { code: CanonicalCode.PERMISSION_DENIED }; + case 401: + return { code: CanonicalCode.UNAUTHENTICATED }; + case 429: + return { code: CanonicalCode.RESOURCE_EXHAUSTED }; + case 501: + return { code: CanonicalCode.UNIMPLEMENTED }; + case 503: + return { code: CanonicalCode.UNAVAILABLE }; + default: + return { code: CanonicalCode.UNKNOWN }; + } + } + } + + /** + * Returns whether the Expect header is on the given options object. + * @param options Options for http.request. + */ + static hasExpectHeader(options: RequestOptions | url.URL): boolean { + if (typeof (options as RequestOptions).headers !== 'object') { + return false; + } + + const keys = Object.keys((options as RequestOptions).headers!); + return !!keys.find(key => key.toLowerCase() === 'expect'); + } + + /** + * Check whether the given obj match pattern + * @param constant e.g URL of request + * @param obj obj to inspect + * @param pattern Match pattern + */ + static satisfiesPattern( + constant: string, + pattern: IgnoreMatcher + ): boolean { + if (typeof pattern === 'string') { + return pattern === constant; + } else if (pattern instanceof RegExp) { + return pattern.test(constant); + } else if (typeof pattern === 'function') { + return pattern(constant); + } else { + throw new TypeError('Pattern is in unsupported datatype'); + } + } + + /** + * Check whether the given request is ignored by configuration + * @param constant e.g URL of request + * @param obj obj to inspect + * @param list List of ignore patterns + */ + static isIgnored(constant: string, list?: IgnoreMatcher[]): boolean { + if (!list) { + // No ignored urls - trace everything + return false; + } + + for (const pattern of list) { + if (Utils.satisfiesPattern(constant, pattern)) { + return true; + } + } + + return false; + } + + /** + * Will subscribe obj on error event and will set attributes when emitting event + * @param span to set + * @param obj to subscribe on error + */ + static setSpanOnError(span: Span, obj: IncomingMessage | ClientRequest) { + obj.on('error', error => { + span.setAttributes({ + [AttributeNames.HTTP_ERROR_NAME]: error.name, + [AttributeNames.HTTP_ERROR_MESSAGE]: error.message, + }); + + let status: Status; + if ((obj as IncomingMessage).statusCode) { + status = Utils.parseResponseStatus( + (obj as IncomingMessage).statusCode! + ); + } else { + status = { code: CanonicalCode.UNKNOWN }; + } + + status.message = error.message; + + span.setStatus(status); + span.end(); + }); + } + + /** + * Makes sure options is an url object + * return an object with default value and parsed options + * @param options for the request + */ + static getRequestInfo( + options: RequestOptions | string, + extraOptions?: RequestOptions + ) { + let pathname = '/'; + let origin = ''; + let optionsParsed: url.URL | url.UrlWithStringQuery | RequestOptions; + + if (typeof options === 'string') { + optionsParsed = url.parse(options); + pathname = (optionsParsed as url.UrlWithStringQuery).pathname || '/'; + origin = `${optionsParsed.protocol || 'http:'}//${optionsParsed.host}`; + if (extraOptions !== undefined) { + Object.assign(optionsParsed, extraOptions); + } + } else { + optionsParsed = options; + try { + pathname = (options as url.URL).pathname; + if (!pathname && options.path) { + pathname = url.parse(options.path).pathname || '/'; + } + origin = `${options.protocol || 'http:'}//${options.host || + `${options.hostname}:${options.port}`}`; + } catch (ignore) {} + } + + if (Utils.hasExpectHeader(optionsParsed)) { + (optionsParsed as RequestOptions).headers = Object.assign( + {}, + (optionsParsed as RequestOptions).headers + ); + } else if (!(optionsParsed as RequestOptions).headers) { + (optionsParsed as RequestOptions).headers = {}; + } + // some packages return method in lowercase.. + // ensure upperCase for consistency + let method = (optionsParsed as RequestOptions).method; + method = method ? method.toUpperCase() : 'GET'; + + return { origin, pathname, method, optionsParsed }; + } +} diff --git a/packages/opentelemetry-plugin-http/test/fixtures/google.json b/packages/opentelemetry-plugin-http/test/fixtures/google.json new file mode 100644 index 00000000000..62301ab56b7 --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/fixtures/google.json @@ -0,0 +1,43 @@ +[ + { + "scope": "http://www.google.com", + "method": "GET", + "path": "/search?q=axios&oq=axios&aqs=chrome.0.69i59l2j0l3j69i60.811j0j7&sourceid=chrome&ie=UTF-8", + "body": "", + "status": 200, + "response": "3c21646f63747970652068746d6c3e3c68746d6c206c616e673d22656e2d4341223e3c686561643e3c6d65746120636861727365743d225554462d38223e3c6d65746120636f6e74656e743d222f696d616765732f6272616e64696e672f676f6f676c65672f31782f676f6f676c65675f7374616e646172645f636f6c6f725f31323864702e706e6722206974656d70726f703d22696d616765223e3c7469746c653e6178696f73202d20476f6f676c65205365617263683c2f7469746c653e3c736372697074206e6f6e63653d224f77774e4a497141533843544d31352f4351595531513d3d223e2866756e6374696f6e28297b76617220613d77696e646f772e706572666f726d616e63653b77696e646f772e73746172743d286e65772044617465292e67657454696d6528293b613a7b76617220623d77696e646f773b69662861297b76617220633d612e74696d696e673b69662863297b76617220643d632e6e617669676174696f6e53746172742c653d632e726573706f6e736553746172743b696628653e642626653c3d77696e646f772e7374617274297b77696e646f772e73746172743d653b622e777372743d652d643b627265616b20617d7d612e6e6f77262628622e777372743d4d6174682e666c6f6f7228612e6e6f77282929297d7d77696e646f772e676f6f676c653d77696e646f772e676f6f676c657c7c7b7d3b676f6f676c652e6166743d66756e6374696f6e2866297b662e7365744174747269627574652822646174612d696d6c222c2b6e65772044617465297d3b7d292e63616c6c2874686973293b2866756e6374696f6e28297b76617220633d5b5d2c653d303b77696e646f772e70696e673d66756e6374696f6e2862297b2d313d3d622e696e6465784f662822267a782229262628622b3d22267a783d222b286e65772044617465292e67657454696d652829293b76617220613d6e657720496d6167652c643d652b2b3b635b645d3d613b612e6f6e6572726f723d612e6f6e6c6f61643d612e6f6e61626f72743d66756e6374696f6e28297b64656c65746520635b645d7d3b612e7372633d627d3b7d292e63616c6c2874686973293b3c2f7363726970743e3c7374796c653e626f64797b6d617267696e3a30206175746f3b6d61782d77696474683a37333670783b70616464696e673a30203870787d617b636f6c6f723a233139363744323b746578742d6465636f726174696f6e3a6e6f6e653b7461702d686967686c696768742d636f6c6f723a7267626128302c302c302c2e31297d613a766973697465647b636f6c6f723a233442313141387d613a686f7665727b746578742d6465636f726174696f6e3a756e6465726c696e657d696d677b626f726465723a307d68746d6c7b666f6e742d66616d696c793a526f626f746f2c48656c7665746963614e6575652c417269616c2c73616e732d73657269663b666f6e742d73697a653a313470783b6c696e652d6865696768743a323070783b746578742d73697a652d61646a7573743a313030253b636f6c6f723a233343343034333b776f72642d777261703a627265616b2d776f72643b6261636b67726f756e642d636f6c6f723a236666667d2e625273576e637b6261636b67726f756e642d636f6c6f723a236666663b626f726465722d746f703a31707820736f6c696420236530653065303b6865696768743a333970783b6f766572666c6f773a68696464656e7d2e4e365257567b6865696768743a353170783b6f766572666c6f772d7363726f6c6c696e673a746f7563683b6f766572666c6f772d783a6175746f3b6f766572666c6f772d793a68696464656e7d2e5576363771627b626f782d7061636b3a6a7573746966793b666f6e742d73697a653a313270783b6c696e652d6865696768743a333770783b6a7573746966792d636f6e74656e743a73706163652d6265747765656e3b6a7573746966792d636f6e74656e743a73706163652d6265747765656e7d2e55763637716220612c2e557636377162207370616e7b636f6c6f723a233735373537353b646973706c61793a626c6f636b3b666c65783a6e6f6e653b70616464696e673a3020313670783b746578742d616c69676e3a63656e7465723b746578742d7472616e73666f726d3a7570706572636173653b7d7370616e2e4f585875707b626f726465722d626f74746f6d3a32707820736f6c696420233432383566343b636f6c6f723a233432383566343b666f6e742d7765696768743a626f6c647d612e655a743878643a766973697465647b636f6c6f723a233735373537357d2e46456c6273667b626f726465722d6c6566743a31707820736f6c6964207267626128302c302c302c2e3132297d6865616465722061727469636c657b6f766572666c6f773a76697369626c657d2e5067373062667b6865696768743a333970783b646973706c61793a626f783b646973706c61793a666c65783b646973706c61793a666c65783b77696474683a313030257d2e4830505165637b706f736974696f6e3a72656c61746976653b666c65783a317d2e7362637b646973706c61793a666c65783b77696474683a313030257d2e50673730626620696e7075747b6d617267696e3a3270782034707820327078203870783b7d2e787b77696474683a323670783b636f6c6f723a233735373537353b666f6e743a323770782f3338707820617269616c2c2073616e732d73657269663b6c696e652d6865696768743a343070783b7d237164436c77627b666c65783a302030206175746f3b77696474683a333970783b6865696768743a333970783b626f726465722d626f74746f6d3a303b70616464696e673a303b626f726465722d746f702d72696768742d7261646975733a3870783b6261636b67726f756e642d636f6c6f723a233362373865373b626f726465723a31707820736f6c696420233333363764363b6261636b67726f756e642d696d6167653a75726c28646174613a696d6167652f6769663b6261736536342c52306c474f4464684a41416a41504948414f44722f6e436b2b4d505a2f466d5639367a4b2b2f372b2f354b352b6b714c39697741414141414a41416a41454144616e693633503477796b6d624b635152584473635141454d586d6d65614c51564c43756b7a79433039416a66654b37762f4d41616a41434c68504d564167776a73556345695a61387867415972567176324b783269777349414141426b6e664242414b7254453449634d796f743875723864617471496251664a646e41666f3257453642563035775849694a69676b414f773d3d293b7d2e73637b666f6e742d73697a653a3b706f736974696f6e3a6162736f6c7574653b746f703a333970783b6c6566743a303b72696768743a303b626f782d736861646f773a3070782032707820357078207267626128302c302c302c302e32293b7a2d696e6465783a323b6261636b67726f756e642d636f6c6f723a236666667d2e73633e6469767b70616464696e673a3130707820313070783b70616464696e672d6c6566743a313670783b70616464696e672d6c6566743a313470783b626f726465722d746f703a31707820736f6c696420234446453145357d2e7363737b6261636b67726f756e642d636f6c6f723a236635663566353b7d2e6e6f484978637b646973706c61793a626c6f636b3b666f6e742d73697a653a313670783b70616464696e673a3020302030203870783b666c65783a313b6865696768743a333570783b6f75746c696e653a6e6f6e653b626f726465723a6e6f6e653b77696474683a313030253b2d7765626b69742d7461702d686967686c696768742d636f6c6f723a7267626128302c302c302c30293b6f766572666c6f773a68696464656e3b7d2e73626320696e7075745b747970653d746578745d7b6261636b67726f756e643a6e6f6e657d2e736d6c202e634f6c3449647b646973706c61793a6e6f6e657d2e6c7b646973706c61793a6e6f6e657d2e736d6c206865616465727b6261636b67726f756e643a6e6f6e657d2e736d6c202e6c7b646973706c61793a626c6f636b3b70616464696e673a30203870787d2e736d6c202e6c7b6c65747465722d73706163696e673a2d3170783b746578742d616c69676e3a63656e7465723b626f726465722d7261646975733a3270782030203020303b666f6e743a323270782f33367078204675747572612c20417269616c2c2073616e732d73657269663b666f6e742d736d6f6f7468696e673a616e7469616c69617365647d2e627a316c42627b6261636b67726f756e643a236666663b626f726465722d7261646975733a38707820387078203020303b626f782d736861646f773a30203170782036707820726762612833322c2033332c2033362c20302e3138293b6d617267696e2d746f703a313070787d2e4b50374c43627b626f726465722d7261646975733a30203020387078203870783b626f782d736861646f773a30203270782033707820726762612833322c2033332c2033362c20302e3138293b6d617267696e2d626f74746f6d3a313070783b6f766572666c6f773a68696464656e7d2e634f6c3449647b6c65747465722d73706163696e673a2d3170783b746578742d616c69676e3a63656e7465723b666f6e743a32327074204675747572612c20417269616c2c2073616e732d73657269663b70616464696e673a3130707820302035707820303b6865696768743a333770783b666f6e742d736d6f6f7468696e673a616e7469616c69617365647d2e634f6c344964207370616e7b646973706c61793a696e6c696e652d626c6f636b7d2e533539316a7b6865696768743a313030257d2e5636677756647b636f6c6f723a233432383546347d2e69576b7576647b636f6c6f723a234541343333357d2e63447251377b636f6c6f723a236662636330357d2e6e746c52397b636f6c6f723a233334413835337d2e744a334d79637b2d7765626b69742d7472616e73666f726d3a726f74617465282d3230646567293b706f736974696f6e3a72656c61746976653b6c6566743a2d3170783b646973706c61793a696e6c696e652d626c6f636b7d666f6f7465727b746578742d616c69676e3a63656e7465723b6d617267696e2d746f703a313870787d666f6f74657220612c666f6f74657220613a766973697465642c2e736d695562627b636f6c6f723a233566363336387d2e6b73545534637b6d617267696e3a3020313370787d236d436c6a6f627b6d617267696e2d746f703a333670787d236d436c6a6f623e6469767b6d617267696e3a323070787d3c2f7374796c653e3c2f686561643e3c626f6479206a736d6f64656c3d2220223e3c6865616465722069643d22686472223e3c736372697074206e6f6e63653d224f77774e4a497141533843544d31352f4351595531513d3d223e2866756e6374696f6e28297b76617220633d3530303b2866756e6374696f6e28297b77696e646f772e73637265656e262677696e646f772e73637265656e2e77696474683c3d63262677696e646f772e73637265656e2e6865696768743c3d632626646f63756d656e742e676574456c656d656e7442794964282268647222292e636c6173734c6973742e6164642822736d6c22293b7d292e63616c6c2874686973293b7d2928293b3c2f7363726970743e3c64697620636c6173733d22634f6c344964223e3c6120687265663d222f3f73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673514f776743223e3c7370616e20636c6173733d22563667775664223e473c2f7370616e3e3c7370616e20636c6173733d2269576b757664223e6f3c2f7370616e3e3c7370616e20636c6173733d226344725137223e6f3c2f7370616e3e3c7370616e20636c6173733d22563667775664223e673c2f7370616e3e3c7370616e20636c6173733d226e746c5239223e6c3c2f7370616e3e3c7370616e20636c6173733d2269576b75766420744a334d7963223e653c2f7370616e3e3c2f613e3c2f6469763e3c64697620636c6173733d22627a316c4262223e3c666f726d20636c6173733d22506737306266222069643d227366223e3c6120636c6173733d226c2220687265663d222f3f6f75747075743d73656172636826616d703b69653d5554462d3826616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735150416745223e3c7370616e20636c6173733d22563667775664223e473c2f7370616e3e3c7370616e20636c6173733d2269576b757664223e6f3c2f7370616e3e3c7370616e20636c6173733d226344725137223e6f3c2f7370616e3e3c7370616e20636c6173733d22563667775664223e673c2f7370616e3e3c7370616e20636c6173733d226e746c5239223e6c3c2f7370616e3e3c7370616e20636c6173733d2269576b75766420744a334d7963223e653c2f7370616e3e3c2f613e3c696e707574206e616d653d226965222076616c75653d2249534f2d383835392d312220747970653d2268696464656e223e3c64697620636c6173733d22483050516563223e3c64697620636c6173733d227362632065736263223e3c696e70757420636c6173733d226e6f48497863222076616c75653d226178696f7322206175746f6361706974616c697a653d226e6f6e6522206175746f636f6d706c6574653d226f666622206e616d653d227122207370656c6c636865636b3d2266616c73652220747970653d2274657874223e3c696e707574206e616d653d226f712220747970653d2268696464656e223e3c696e707574206e616d653d226171732220747970653d2268696464656e223e3c64697620636c6173733d2278223ed73c2f6469763e3c64697620636c6173733d227363223e3c2f6469763e3c2f6469763e3c2f6469763e3c627574746f6e2069643d227164436c77622220747970653d227375626d6974223e3c2f627574746f6e3e3c2f666f726d3e3c2f6469763e3c6e6f7363726970743e3c6d65746120636f6e74656e743d22303b75726c3d2f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b6762763d3126616d703b7365693d704d524f586250624472505339414f426d4b505942512220687474702d65717569763d2272656672657368223e3c7374796c653e7461626c652c6469762c7370616e2c707b646973706c61793a6e6f6e657d3c2f7374796c653e3c646976207374796c653d22646973706c61793a626c6f636b223e506c6561736520636c69636b203c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b6762763d3126616d703b7365693d704d524f586250624472505339414f426d4b50594251223e686572653c2f613e20696620796f7520617265206e6f7420726564697265637465642077697468696e206120666577207365636f6e64732e3c2f6469763e3c2f6e6f7363726970743e3c2f6865616465723e3c6469762069643d226d61696e223e3c6469763e3c64697620636c6173733d224b50374c4362223e203c64697620636c6173733d22625273576e63223e203c64697620636c6173733d224e36525756223e203c64697620636c6173733d2250673730626620557636377162223e203c7370616e20636c6173733d224f58587570223e416c6c3c2f7370616e3e3c6120636c6173733d22655a743878642220687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e6d7326616d703b74626d3d6e777326616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673515f41554943436742223e4e6577733c2f613e3c6120636c6173733d22655a743878642220687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e6d7326616d703b74626d3d76696426616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673515f41554943536743223e566964656f733c2f613e3c6120636c6173733d22655a743878642220687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e6d7326616d703b74626d3d6973636826616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673515f41554943696744223e496d616765733c2f613e2020203c6120687265663d22687474703a2f2f6d6170732e676f6f676c652e636f6d2f6d6170733f713d6178696f7326616d703b756d3d3126616d703b69653d5554462d3826616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673515f41554943796745223e4d6170733c2f613e20203c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e6d7326616d703b74626d3d73686f7026616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673515f41554944436746223e53686f7070696e673c2f613e20203c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e6d7326616d703b74626d3d626b7326616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673515f41554944536747223e426f6f6b733c2f613e20202020203c64697620636c6173733d2246456c627366223e3c6120687265663d222f616476616e6365645f73656172636822207374796c653d2277686974652d73706163653a6e6f77726170222069643d2273742d746f67676c652220726f6c653d22627574746f6e223e53656172636820746f6f6c733c2f613e3c2f6469763e203c2f6469763e203c2f6469763e203c2f6469763e203c2f6469763e3c64697620636c6173733d22506737306266207745736a6264205a494e62626320787064204f396735636320755550476922207374796c653d22646973706c61793a6e6f6e65222069643d2273742d63617264223e3c7374796c653e2e7745736a62647b6261636b67726f756e642d636f6c6f723a236666663b6865696768743a343470783b77686974652d73706163653a6e6f777261707d2e636f505538637b6865696768743a363070783b6f766572666c6f772d7363726f6c6c696e673a746f7563683b6f766572666c6f772d783a6175746f3b6f766572666c6f772d793a68696464656e7d2e586a326175657b6865696768743a343470783b6f766572666c6f773a68696464656e7d2e526e4e477a657b6d617267696e3a3131707820313670787d2e7745736a6264206469762c2e7745736a626420612c2e7745736a6264206c697b6f75746c696e652d77696474683a303b6f75746c696e653a6e6f6e657d3c2f7374796c653e3c64697620636c6173733d22586a32617565223e3c64697620636c6173733d22636f50553863223e3c64697620636c6173733d22526e4e477a65223e3c7374796c653e2e5041394a357b646973706c61793a696e6c696e652d626c6f636b7d2e5258614f66647b646973706c61793a696e6c696e652d626c6f636b3b6865696768743a323270783b706f736974696f6e3a72656c61746976653b70616464696e672d746f703a303b70616464696e672d626f74746f6d3a303b70616464696e672d72696768743a313670783b70616464696e672d6c6566743a303b6c696e652d6865696768743a323270783b637572736f723a706f696e7465723b746578742d7472616e73666f726d3a7570706572636173653b666f6e742d73697a653a313270783b636f6c6f723a233735373537357d2e736131746f637b646973706c61793a6e6f6e653b706f736974696f6e3a6162736f6c7574653b6261636b67726f756e643a236666663b626f726465723a31707820736f6c696420236436643664363b626f782d736861646f773a302032707820347078207267626128302c302c302c302e33293b6d617267696e3a303b77686974652d73706163653a6e6f777261703b7a2d696e6465783a3130333b6c696e652d6865696768743a313770783b70616464696e672d746f703a3570783b70616464696e672d626f74746f6d3a3570783b70616464696e672d6c6566743a3070787d2e5041394a353a686f766572202e736131746f637b646973706c61793a626c6f636b7d2e6d475379386420613a6163746976652c2e5258614f66643a6163746976657b636f6c6f723a233432383566347d3c2f7374796c653e3c64697620636c6173733d225041394a35223e3c64697620636c6173733d225258614f66642220726f6c653d22627574746f6e2220746162696e6465783d2230223e3c7374796c653e2e54574d4f55637b646973706c61793a696e6c696e652d626c6f636b3b70616464696e672d72696768743a313470783b77686974652d73706163653a6e6f777261707d2e7651597547667b666f6e742d7765696768743a626f6c647d2e4f6d54497a667b626f726465722d636f6c6f723a23393039303930207472616e73706172656e743b626f726465722d7374796c653a736f6c69643b626f726465722d77696474683a347078203470782030203470783b77696474683a303b6865696768743a303b6d617267696e2d6c6566743a2d313070783b746f703a3530253b6d617267696e2d746f703a2d3270783b706f736974696f6e3a6162736f6c7574657d2e5258614f66643a616374697665202e4f6d54497a667b626f726465722d636f6c6f723a23343238356634207472616e73706172656e747d3c2f7374796c653e3c64697620636c6173733d2254574d4f5563223e416e792074696d653c2f6469763e3c7370616e20636c6173733d224f6d54497a66223e3c2f7370616e3e3c2f6469763e3c756c20636c6173733d22736131746f63206f7a61744d223e3c7374796c653e2e6f7a61744d7b666f6e742d73697a653a313270783b746578742d7472616e73666f726d3a7570706572636173657d2e6f7a61744d202e794e46736c2c2e6f7a61744d206c697b6c6973742d7374796c652d747970653a6e6f6e653b6c6973742d7374796c652d706f736974696f6e3a6f7574736964653b6c6973742d7374796c652d696d6167653a6e6f6e657d2e794e46736c2e536b556a34632c2e794e46736c20617b636f6c6f723a7267626128302c302c302c302e3534293b746578742d6465636f726174696f6e3a6e6f6e653b70616464696e673a36707820343470782036707820313470783b6c696e652d6865696768743a313770783b646973706c61793a626c6f636b7d2e536b556a34637b6261636b67726f756e642d696d6167653a75726c282f2f73736c2e677374617469632e636f6d2f75692f76312f6d656e752f636865636b6d61726b322e706e67293b6261636b67726f756e642d706f736974696f6e3a72696768742063656e7465723b6261636b67726f756e642d7265706561743a6e6f2d7265706561747d2e536b556a34633a6163746976657b6261636b67726f756e642d636f6c6f723a236635663566357d3c2f7374796c653e3c6c6920636c6173733d22794e46736c20536b556a3463223e416e792074696d653c2f6c693e3c6c6920636c6173733d22794e46736c223e3c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e7426616d703b7462733d7164723a6826616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351707755494477223e5061737420686f75723c2f613e3c2f6c693e3c6c6920636c6173733d22794e46736c223e3c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e7426616d703b7462733d7164723a6426616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351707755494541223e5061737420323420686f7572733c2f613e3c2f6c693e3c6c6920636c6173733d22794e46736c223e3c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e7426616d703b7462733d7164723a7726616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351707755494551223e50617374207765656b3c2f613e3c2f6c693e3c6c6920636c6173733d22794e46736c223e3c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e7426616d703b7462733d7164723a6d26616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351707755494567223e50617374206d6f6e74683c2f613e3c2f6c693e3c6c6920636c6173733d22794e46736c223e3c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e7426616d703b7462733d7164723a7926616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351707755494577223e5061737420796561723c2f613e3c2f6c693e3c2f756c3e3c2f6469763e3c64697620636c6173733d225041394a35223e3c64697620636c6173733d225258614f66642220726f6c653d22627574746f6e2220746162696e6465783d2230223e3c64697620636c6173733d2254574d4f5563223e416c6c20726573756c74733c2f6469763e3c7370616e20636c6173733d224f6d54497a66223e3c2f7370616e3e3c2f6469763e3c756c20636c6173733d22736131746f63206f7a61744d223e3c6c6920636c6173733d22794e46736c20536b556a3463223e416c6c20726573756c74733c2f6c693e3c6c6920636c6173733d22794e46736c223e3c6120687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b736f757263653d6c6e7426616d703b7462733d6c693a3126616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351707755494651223e566572626174696d3c2f613e3c2f6c693e3c2f756c3e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c736372697074206e6f6e63653d224f77774e4a497141533843544d31352f4351595531513d3d223e2866756e6374696f6e28297b76617220613d646f63756d656e742e676574456c656d656e7442794964282273742d746f67676c6522292c623d646f63756d656e742e676574456c656d656e7442794964282273742d6361726422293b612626622626612e6164644576656e744c697374656e65722822636c69636b222c66756e6374696f6e2863297b622e7374796c652e646973706c61793d622e7374796c652e646973706c61793f22223a226e6f6e65223b632e70726576656e7444656661756c7428297d2c2131293b7d292e63616c6c2874686973293b3c2f7363726970743e3c2f6469763e3c2f6469763e3c7374796c653e2e5a494e6262637b6261636b67726f756e642d636f6c6f723a236666663b6d617267696e2d626f74746f6d3a313070783b626f782d736861646f773a30203170782036707820726762612833322c2033332c2033362c20302e3238293b626f726465722d7261646975733a3870787d2e75555047697b666f6e742d73697a653a313470783b6c696e652d6865696768743a323070783b7d2e4f39673563633e2a3a66697273742d6368696c647b626f726465722d746f702d6c6566742d7261646975733a3870783b626f726465722d746f702d72696768742d7261646975733a3870787d2e4f39673563633e2a3a6c6173742d6368696c647b626f726465722d626f74746f6d2d6c6566742d7261646975733a3870783b626f726465722d626f74746f6d2d72696768742d7261646975733a3870787d2e726c37696c627b646973706c61793a626c6f636b3b636c6561723a626f74687d2e6c634a4631647b6d617267696e2d6c6566743a313670783b666c6f61743a72696768743b7d2e6b437259547b70616464696e673a31327078203136707820313270787d612e6664597371667b636f6c6f723a233442313141387d2e424e656177657b77686974652d73706163653a7072652d6c696e653b776f72642d777261703a627265616b2d776f72647d2e76766a774a627b636f6c6f723a233139363744323b666f6e742d73697a653a313670783b6c696e652d6865696768743a323070787d2e76766a774a6220613a766973697465647b636f6c6f723a233442313141387d2e76766a774a622e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c31297d2e76766a774a622e48724764656220613a766973697465647b636f6c6f723a72676261283235352c3235352c3235352c2e37297d2e55506d69747b666f6e742d73697a653a313470783b6c696e652d6865696768743a323070787d2e55506d69742e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c2e37297d2e55506d69742e415037576e647b636f6c6f723a7267626128302c3130322c33332c31297d2e7835346774667b6865696768743a3170783b6261636b67726f756e642d636f6c6f723a236466653165353b6d617267696e3a3020313670787d2e4170354f53647b70616464696e672d626f74746f6d3a313270787d2e7333763972647b666f6e742d73697a653a313470783b6c696e652d6865696768743a323070787d2e7333763972642e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c31297d2e7333763972642e415037576e647b636f6c6f723a233230323132347d2e6d53783145657b70616464696e672d6c6566743a343870783b6d617267696e3a307d2e7639693631657b70616464696e672d626f74746f6d3a3870787d2e584c6c6f58657b636f6c6f723a233139363744323b666f6e742d73697a653a313470783b6c696e652d6865696768743a323070787d2e584c6c6f586520613a766973697465647b636f6c6f723a233442313141387d2e584c6c6f58652e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c31297d2e584c6c6f58652e48724764656220613a766973697465647b636f6c6f723a72676261283235352c3235352c3235352c2e37297d2e5a54763942627b646973706c61793a626c6f636b7d2e6465497643627b666f6e742d73697a653a313670783b6c696e652d6865696768743a323070783b666f6e742d7765696768743a3430307d2e6465497643622e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c31297d2e6465497643622e415037576e647b636f6c6f723a233230323132347d2e4643557030637b666f6e742d7765696768743a626f6c647d2e58374e5456657b646973706c61793a7461626c653b77696474683a313030253b70616464696e672d72696768743a313670783b626f782d73697a696e673a626f726465722d626f787d2e74486d6651657b646973706c61793a7461626c652d63656c6c3b70616464696e673a313270782030203132707820313670787d2e554874726b7b77696474683a373270787d2e4842544d36647b77696474683a333070787d2e5853377947647b646973706c61793a7461626c652d63656c6c3b746578742d616c69676e3a63656e7465723b766572746963616c2d616c69676e3a6d6964646c653b70616464696e673a3132707820302031327078203870787d2e616d335142667b646973706c61793a7461626c653b766572746963616c2d616c69676e3a746f707d2e5862355652657b636f6c6f723a233139363744327d613a76697369746564202e5862355652657b636f6c6f723a233442313141387d2e5862355652652e74723064777b636f6c6f723a72676261283235352c3235352c3235352c31297d613a76697369746564202e5862355652652e74723064777b636f6c6f723a72676261283235352c3235352c3235352c2e37297d2e74416438447b666f6e742d73697a653a313470783b6c696e652d6865696768743a323070787d2e74416438442e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c2e37297d2e74416438442e415037576e647b636f6c6f723a233730373537417d2e614a79694f637b636f6c6f723a233030363632317d2e6f754a3943627b666c6f61743a6c6566743b70616464696e672d72696768743a313670787d2e526f434c6e657b636c6561723a626f74687d2e45594f736c647b646973706c61793a696e6c696e652d626c6f636b3b706f736974696f6e3a72656c61746976657d2e424669395a627b6f766572666c6f773a68696464656e3b706f736974696f6e3a72656c61746976657d2e53374a647a657b616c69676e2d6974656d733a63656e7465723b646973706c61793a666c65783b666c65782d646972656374696f6e3a636f6c756d6e3b6a7573746966792d636f6e74656e743a73706163652d61726f756e647d2e58646c7230647b6f766572666c6f772d783a6175746f3b2d7765626b69742d6f766572666c6f772d7363726f6c6c696e673a746f7563683b6d617267696e3a30202d3870783b70616464696e673a313670782030203136707820323470783b70616464696e672d746f703a3270783b6d617267696e2d746f703a2d3270783b7472616e73666f726d3a7472616e736c617465336428302c302c30297d2e6964673862657b646973706c61793a7461626c653b626f726465722d636f6c6c617073653a73657061726174653b626f726465722d73706163696e673a38707820303b6d617267696e3a30202d3870783b70616464696e672d72696768743a313670787d2e425647304e627b646973706c61793a7461626c652d63656c6c3b766572746963616c2d616c69676e3a746f703b6261636b67726f756e642d636f6c6f723a236666663b626f726465722d7261646975733a3870783b626f782d736861646f773a30203170782036707820726762612833322c2033332c2033362c20302e3238293b6f766572666c6f773a68696464656e7d2e55796b5439647b646973706c61793a626c6f636b3b666c6f61743a72696768743b70616464696e672d6c6566743a313670787d2e6e59543751627b636c6561723a626f74687d2e736b566770627b646973706c61793a7461626c653b7461626c652d6c61796f75743a66697865643b77696474683a313030257d2e5647484d58647b646973706c61793a7461626c652d63656c6c3b766572746963616c2d616c69676e3a6d6964646c653b6865696768743a353270783b746578742d616c69676e3a63656e7465727d2e4c70614472627b6d617267696e3a30206175746f203870783b646973706c61793a626c6f636b7d2e766253684f657b70616464696e672d746f703a307d2e4156736570667b70616464696e672d626f74746f6d3a3870787d2e4156736570662e753278314f647b70616464696e672d626f74746f6d3a307d2e787063202e6877632c2e787078202e6877787b646973706c61793a6e6f6e657d2e5275386964627b6d617267696e2d746f703a2d313670787d2e70756e657a7b666f6e742d7765696768743a3730303b6c65747465722d73706163696e673a302e373570783b746578742d7472616e73666f726d3a7570706572636173657d2e7779727758637b666f6e742d73697a653a313270783b6c696e652d6865696768743a313670787d2e7779727758632e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c31297d2e7779727758632e415037576e647b636f6c6f723a233230323132347d2e6d4868796c667b646973706c61793a7461626c652d63656c6c3b766572746963616c2d616c69676e3a6d6964646c657d2e575a35474a667b616c69676e2d6974656d733a63656e7465723b70616464696e673a3020323070783b6d696e2d77696474683a31313270787d2e714e394b65642c2e44586b354d657b6d617267696e3a30206175746f7d2e44586b354d657b6d617267696e2d626f74746f6d3a313270787d2e51693946647b6261636b67726f756e643a236666663b626f726465723a303b626f726465722d7261646975733a39393970783b646973706c61793a626c6f636b3b6865696768743a353670783b6a7573746966792d636f6e74656e743a63656e7465723b77696474683a353670783b7a2d696e6465783a307d2e51693946647b626f782d736861646f773a30203170782036707820726762612833322c2033332c2033362c20302e3238292c696e7365742030203020302030207267626128302c302c302c302e3130292c696e73657420302030203020302072676261283235352c3235352c3235352c302e3530297d2e51693946643a666f6375737b6f75746c696e653a6e6f6e657d2e5169394664202e685748754a7b646973706c61793a626c6f636b3b6d617267696e3a30206175746f7d2e6a69356a70667b746578742d616c69676e3a63656e7465727d68727b626f726465723a303b626f726465722d626f74746f6d3a31707820736f6c696420236466653165353b6d617267696e3a303b7d2e425579624b652c2e48736e4642667b6d617267696e2d6c6566743a313670787d2e425579624b652c2e6f4d3247417b6d617267696e2d72696768743a313670787d2e584f377268637b6d617267696e3a30202d313670787d2e6949576d34627b626f782d73697a696e673a626f726465722d626f783b6d696e2d6865696768743a343870787d2e664c745873637b70616464696e673a313470783b746578742d616c69676e3a63656e7465727d2e4c796d38577b77696474683a313470783b6865696768743a323070783b706f736974696f6e3a72656c61746976653b6d617267696e3a30206175746f7d2e4164665872627b6d617267696e2d6c6566743a2d313470783b766572746963616c2d616c69676e3a6d6964646c653b646973706c61793a696e6c696e652d626c6f636b7d2e4c796d3857206469767b706f736974696f6e3a6162736f6c7574653b626f726465722d6c6566743a37707820736f6c6964207472616e73706172656e743b626f726465722d72696768743a37707820736f6c6964207472616e73706172656e743b77696474683a303b6865696768743a303b6c6566743a307d2e4979596145647b746f703a3770783b626f726465722d746f703a37707820736f6c696420233735373537357d2e4543554851657b746f703a3470783b626f726465722d746f703a37707820736f6c696420236666667d2e4165515175627b626f74746f6d3a3770783b626f726465722d626f74746f6d3a37707820736f6c696420233735373537357d2e5943553765627b626f74746f6d3a3470783b626f726465722d626f74746f6d3a37707820736f6c696420236666667d2e4963783643647b6d617267696e3a30206175746f203870787d2e6d41646a51637b746578742d616c69676e3a72696768747d2e75456563337b666f6e742d73697a653a313270783b6c696e652d6865696768743a313670787d2e75456563332e4872476465627b636f6c6f723a72676261283235352c3235352c3235352c2e37297d2e75456563332e415037576e647b636f6c6f723a233730373537417d2e724c736879662c2e426d503574667b70616464696e672d746f703a313270783b70616464696e672d626f74746f6d3a313270787d2e773143334c652c2e426d503574662c2e47354e6242647b70616464696e672d6c6566743a313670783b70616464696e672d72696768743a313670783b7d2e47354e6242647b70616464696e672d626f74746f6d3a313270787d2e6e4d796d65667b646973706c61793a666c65787d2e473565466c667b666c65783a313b646973706c61793a626c6f636b7d2e6e4d796d6566207370616e7b746578742d616c69676e3a63656e7465727d3c2f7374796c653e3c6469763e3c212d2d53575f435f582d2d3e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f26616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351466a4141656751494278414226616d703b7573673d414f76566177327466496430584346385649396a682d324f35555069223e3c64697620636c6173733d22424e656177652076766a774a6220415037576e64223e4178696f733c2f6469763e3c64697620636c6173733d22424e656177652055506d697420415037576e64223e68747470733a2f2f7777772e6178696f732e636f6d3c2f6469763e3c2f613e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d226b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c6469763e3c6469763e3c64697620636c6173733d224170354f5364223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e536d6172742c20656666696369656e74206e65777320776f72746879206f6620796f75722074696d652c20617474656e74696f6e2c20616e642074727573742e3c2f6469763e3c2f6469763e3c64697620636c6173733d22763969363165223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e20636c6173733d22424e65617765223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f706f6c697469637326616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673516a42417741586f4543416351417726616d703b7573673d414f765661773139685044464b57637a653030627974527851476576223e3c7370616e20636c6173733d22584c6c6f586520415037576e64223e506f6c69746963733c2f7370616e3e3c2f613e3c2f7370616e3e3c2f6469763e3c2f6469763e3c64697620636c6173733d22763969363165223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e20636c6173733d22424e65617765223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f617574686f72732f6e6577736465736b26616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673516a424177416e6f4543416351425126616d703b7573673d414f7656617730315a6b4f30626d50386e4370657152386257474959223e3c7370616e20636c6173733d22584c6c6f586520415037576e64223e53746f72696573206279204178696f733c2f7370616e3e3c2f613e3c2f7370616e3e3c2f6469763e3c2f6469763e3c64697620636c6173733d22763969363165223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e20636c6173733d22424e65617765223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f6178696f732d64617368626f6172642d313535303631393334332d64653734306436612d656163312d343539392d383262642d3434303533343537376639322e68746d6c26616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673516a42417741336f4543416351427726616d703b7573673d414f76566177305658706145566f434f444153557657784f61466652223e3c7370616e20636c6173733d22584c6c6f586520415037576e64223e4178696f732044617368626f6172643c2f7370616e3e3c2f613e3c2f7370616e3e3c2f6469763e3c2f6469763e3c64697620636c6173733d22763969363165223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e20636c6173733d22424e65617765223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f6e6577736c65747465727326616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673516a42417742486f4543416351435126616d703b7573673d414f76566177316255574b54564d7a4944436d736a34536d614c4755223e3c7370616e20636c6173733d22584c6c6f586520415037576e64223e4e6577736c6574746572733c2f7370616e3e3c2f613e3c2f7370616e3e3c2f6469763e3c2f6469763e3c64697620636c6173733d22763969363165223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e20636c6173733d22424e65617765223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f776f726c6426616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673516a42417742586f4543416351437726616d703b7573673d414f765661773246776f732d5f6e416d39666e4963486a754b325a53223e3c7370616e20636c6173733d22584c6c6f586520415037576e64223e576f726c643c2f7370616e3e3c2f613e3c2f7370616e3e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e20636c6173733d22424e65617765223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f627573696e65737326616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673516a424177426e6f4543416351445126616d703b7573673d414f76566177324366426a4a724a72716e5052623241754d614e4953223e3c7370616e20636c6173733d22584c6c6f586520415037576e64223e427573696e6573733c2f7370616e3e3c2f613e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e3c7370616e20636c6173733d224643557030632072514d516f64223e546f702073746f726965733c2f7370616e3e3c2f6469763e3c2f7370616e3e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f6e6577736c6574746572732f6178696f732d616d2d61653065626362352d616432352d346531632d623662642d3138633530393464656330312e68746d6c26616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351714f63424d4164364241674145414926616d703b7573673d414f765661773148445a7242345437756b327137384256725676426f223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e3c7370616e20636c6173733d2272514d516f6420586235565265223e4178696f7320414d202d2041756775737420392c20323031393c2f7370616e3e3c2f6469763e3c2f7370616e3e3c7370616e3e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e3c7370616e20636c6173733d2272514d516f6420614a79694f63223e4178696f733c2f7370616e3e20b72031206461792061676f3c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f636f756e74726965732d6d6f73742d7269736b2d77617465722d6372697369732d66343066343866392d623032652d346536622d393965642d3136346335636239353333352e68746d6c26616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351714f63424d4168364241674145415526616d703b7573673d414f7656617733505243566f5a5532675a494b44336b62615072544e223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e3c7370616e20636c6173733d2272514d516f6420586235565265223e54686520636f756e7472696573206d6f7374206174207269736b206f662061207761746572206372697369733c2f7370616e3e3c2f6469763e3c2f7370616e3e3c7370616e3e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e3c7370616e20636c6173733d2272514d516f6420614a79694f63223e4178696f733c2f7370616e3e20b72031206461792061676f3c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c6469763e3c6120687265663d222f75726c3f713d68747470733a2f2f747769747465722e636f6d2f6178696f732533467265665f73726325334474777372632532353545676f6f676c652532353743747763616d702532353545736572702532353743747767722532353545617574686f7226616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673513646347743586f4543415951416726616d703b7573673d414f7656617733626475774e6f5934356137397a30464775566e566d223e3c64697620636c6173733d226b43725954223e3c64697620636c6173733d226f754a3943622053374a647a6522207374796c653d2277696474683a343070783b6865696768743a34307078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a343070783b6d61782d6865696768743a34307078222069643d2264696d675f312220646174612d64656665727265643d2231223e3c2f6469763e3c6469763e3c64697620636c6173733d22424e656177652076766a774a6220415037576e64223e4178696f7320262331303030333b3c2f6469763e3c64697620636c6173733d22424e656177652055506d697420415037576e64223e54776974746572202623383235303b206178696f733c2f6469763e3c2f6469763e3c64697620636c6173733d22526f434c6e65223e3c2f6469763e3c2f6469763e3c2f613e3c2f6469763e3c6469763e3c6469763e3c64697620636c6173733d2258646c723064223e3c64697620636c6173733d22696467386265223e3c6120636c6173733d22425647304e622220687265663d222f75726c3f713d68747470733a2f2f747769747465722e636f6d2f6178696f732f7374617475732f313135393838373636383733393733393634382533467265665f73726325334474777372632532353545676f6f676c652532353743747763616d702532353545736572702532353743747767722532353545747765657426616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351676c5177436e6f4543415951424126616d703b7573673d414f7656617733765932387a4a6374626a64305a365357505a344c63223e3c6469763e3c646976207374796c653d2277696474683a3233327078223e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e5477697474657220686173207265696e737461746564204d69746368204d63436f6e6e656c6c27732072652d656c656374696f6e2063616d706169676e206163636f756e74206166746572207468652070726f66696c65207761732073757370656e646564206561726c6965722074686973207765656b2e207777772e6178696f732e636f6d2f6d697463682d6d63636f6e2623383233303b3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e313920686f7572732061676f3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f75726c3f713d68747470733a2f2f747769747465722e636f6d2f6178696f732f7374617475732f313135393838333433333933313336363430322533467265665f73726325334474777372632532353545676f6f676c652532353743747763616d702532353545736572702532353743747767722532353545747765657426616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351676c517743336f4543415951426726616d703b7573673d414f76566177336c4b50674566354d5351724b75784655584d614f41223e3c6469763e3c646976207374796c653d2277696474683a3233327078223e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e4e657720746563686e6f6c6f677920697320726573686170696e67206173736574206d616e6167656d656e7420666f722061206e65772067656e65726174696f6e206f6620776f726b6572732c20616e64206d6f7374206f662074686520696e64757374727920686173206e6f74206b65707420706163652e207777772e6178696f732e636f6d2f61737365742d6d616e61672623383233303b3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e323020686f7572732061676f3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f75726c3f713d68747470733a2f2f747769747465722e636f6d2f6178696f732f7374617475732f313135393837373735323139323939353333332533467265665f73726325334474777372632532353545676f6f676c652532353743747763616d702532353545736572702532353743747767722532353545747765657426616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351676c517744486f4543415951434126616d703b7573673d414f7656617732554b4158715044627577556a736d37626342675874223e3c6469763e3c646976207374796c653d2277696474683a3233327078223e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e5374657068656e20526f73732022667265616b6564206f75742220617420746865206261636b6c61736820746f20686973205472756d702066756e64726169736572206173205472756d70206173736f636961746573207065727375616465642068696d20746f20676f206168656164207769746820746865206576656e742061742068697320536f757468616d70746f6e206d616e73696f6e2e207777772e6178696f732e636f6d2f7374657068656e2d726f732623383233303b3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e323020686f7572732061676f3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f75726c3f713d68747470733a2f2f747769747465722e636f6d2f6178696f732f7374617475732f313135393837323436373432383530373634392533467265665f73726325334474777372632532353545676f6f676c652532353743747763616d702532353545736572702532353743747767722532353545747765657426616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351676c517744586f4543415951436726616d703b7573673d414f7656617733683854556374412d32397252656e584742497a7a5f223e3c6469763e3c646976207374796c653d2277696474683a3233327078223e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e4265796f6e6420726570616972696e6720616e6420696d70726f76696e6720726f61647320616e64207369646577616c6b732c20636974696573206861766520616e206f70706f7274756e69747920746f206275696c6420696e667261737472756374757265207468617420636f756c64206f70656e207570206e6577206d6f62696c697479206f7074696f6e7320616e6420696e637265617365206163636573736962696c6974792c207772697465732045787065727420566f6963657320636f6e7472696275746f722048656e727920436c6179706f6f6c2e207777772e6178696f732e636f6d2f616d642d6c6973612d73752623383233303b3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e323020686f7572732061676f3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f75726c3f713d68747470733a2f2f747769747465722e636f6d2f6178696f732f7374617475732f313135393836373138323634333430343830322533467265665f73726325334474777372632532353545676f6f676c652532353743747763616d702532353545736572702532353743747767722532353545747765657426616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351676c5177446e6f4543415951444126616d703b7573673d414f765661773056326641356f4573505f6b723267467962676b5568223e3c6469763e3c646976207374796c653d2277696474683a3233327078223e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e414d442043454f204c697361205375206469736375737365732077697468204178696f732068657220636f6d70616e79277320726573757267656e636520616e6420626174746c6573207769746820496e74656c207777772e6178696f732e636f6d2f616d642d6c6973612d73752623383233303b3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e323120686f7572732061676f3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c6120687265663d222f75726c3f713d68747470733a2f2f6769746875622e636f6d2f6178696f732f6178696f7326616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351466a4150656751494242414226616d703b7573673d414f7656617733486a474b6775516d6342745456445566344d665072223e3c64697620636c6173733d22424e656177652076766a774a6220415037576e64223e6178696f732f6178696f733a2050726f6d697365206261736564204854545020636c69656e7420666f72207468652062726f7773657220616e64202e2e2e202d204769744875623c2f6469763e3c64697620636c6173733d22424e656177652055506d697420415037576e64223e68747470733a2f2f6769746875622e636f6d202623383235303b206178696f73202623383235303b206178696f733c2f6469763e3c2f613e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d226b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c6469763e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e636f6e7374206178696f73203d207265717569726528276178696f7327293b202f2f204d616b652061207265717565737420666f72206120757365722077697468206120676976656e204944206178696f732e2067657428272f757365723f49443d31323334352729202e7468656e2866756e6374696f6e2028726573706f6e736529207b202f2f2068616e646c652073756363657373a02e2e2e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e6178696f732e636f6d2f26616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673517757347745486f4543416b51416726616d703b7573673d414f7656617733565033564769536e46355355474c45653730387037223e3c696d6720636c6173733d2255796b5439642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a373270783b6d61782d6865696768743a37327078222069643d2264696d675f32332220646174612d64656665727265643d2231223e3c2f613e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e4178696f733c2f6469763e3c2f7370616e3e3c7370616e3e3c64697620636c6173733d22424e6561776520744164384420415037576e64223e576562736974653c2f6469763e3c2f7370616e3e3c64697620636c6173733d226e5954375162223e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d226b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e4178696f7320697320616e20416d65726963616e206e65777320616e6420696e666f726d6174696f6e207765627369746520666f756e64656420696e203230313620627920666f726d657220506f6c697469636f207374616666657273204a696d2056616e64654865692c204d696b6520416c6c656e2c20616e6420526f7920536368776172747a2e204974206f6666696369616c6c79206c61756e6368656420696e20323031372e2054686520736974652773206e616d65206973206261736564206f6e2074686520477265656b3a202623373934303b26233935383b26233935333b26233935393b26233936323b2c206d65616e696e672022776f72746879222e203c7370616e20636c6173733d22424e65617765223e3c6120687265663d222f75726c3f713d68747470733a2f2f656e2e77696b6970656469612e6f72672f77696b692f4178696f735f28776562736974652926616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673516d684d77456e6f4543416b51427726616d703b7573673d414f76566177306a76706f32364a65425a684a7638496157384e7163223e3c7370616e20636c6173733d22584c6c6f586520415037576e64223e57696b6970656469613c2f7370616e3e3c2f613e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c64697620636c6173733d22766253684f65206b43725954223e3c64697620636c6173733d22415673657066223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e3e3c7370616e20636c6173733d22424e656177652073337639726420415037576e64223e4f776e65723c2f7370616e3e3c2f7370616e3e3a203c7370616e3e3c7370616e20636c6173733d22424e6561776520744164384420415037576e64223e4178696f73204d6564696120496e633c2f7370616e3e3c2f7370616e3e3c2f6469763e3c2f6469763e3c64697620636c6173733d2241567365706620753278314f64223e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c7370616e3e3c7370616e20636c6173733d22424e656177652073337639726420415037576e64223e4c61756e636865643c2f7370616e3e3c2f7370616e3e3a203c7370616e3e3c7370616e20636c6173733d22424e6561776520744164384420415037576e64223e323031373c2f7370616e3e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e3c7370616e20636c6173733d224643557030632072514d516f64223e50656f706c6520616c736f2073656172636820666f723c2f7370616e3e3c2f6469763e3c2f7370616e3e3c2f6469763e3c64697620636c6173733d22787063223e3c64697620636c6173733d22783534677466223e3c2f6469763e3c6469763e3c64697620636c6173733d226b43725954223e3c7370616e20636c6173733d2270756e657a223e3c64697620636c6173733d22424e656177652077797277586320415037576e64223e4c69626572616c206e65777320736f7572636573206c6973743c2f6469763e3c2f7370616e3e3c2f6469763e3c6469763e3c6469763e3c64697620636c6173733d2258646c723064223e3c64697620636c6173733d22696467386265223e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d434e4e26616d703b737469636b3d483473494141414141414141414f4f5155654c517a3955335343394f4c7a4b537a4d6c4d5369314b7a4648495379307656696a4f4c79314b54693157794d6b734c6f6c6954617a497a43382d7851685866497152433851304d6f38334b726545636a4a79697772536b36434b6b67324b633644693859565a35686e707678676c6658435a333844437549695632646e504477435833376e476b414141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a3642416749454149223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f332220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e434e4e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d506f6c697469636f26616d703b737469636b3d483473494141414141414141414f4f5155654c537a3955334d444b504e7971334e4a4c4d7955784b4c55724d5563684c4c5339574b4d34764c55704f4c5662497953777569574a4e724d6a4d4c7a3746794146536e6c3663586e534b45556b6e6c4a4f52573153516e6752566c4778516e414d566a795f4d4d7339495f38556f3659504c5f415957786b5773484148354f5a6b6c6d636e3541484749687547584141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a364241674945414d223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f352220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e506f6c697469636f3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d48756666506f737426616d703b737469636b3d483473494141414141414141414f4f5155654c537a395533794d67744b6b68504d704c4d7955784b4c55724d5563684c4c5339574b4d34764c55704f4c5662497953777569574a4e724d6a4d4c7a3746794146536e6c3663586e534b45617a5479447a65714e7753796f45594131575562464363417857504c3877797a306a5f78536a7067387638426862475261776348715670615148357853554175376a463070634141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a3642416749454151223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f372220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e48756666506f73743c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d4e505226616d703b737469636b3d483473494141414141414141414f4f5155654c517a3955335344596f7a6a47537a4d6c4d5369314b7a4648495379307656696a4f4c79314b54693157794d6b734c6f6c6954617a497a43382d785168576e463663586e534b6b5176454e444b504e79713368484979636f734b30704f67696b416d5173586a4337504d4d394a5f4d55723634444b5f675956784553757a583041514142514b584c32514141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a3642416749454155223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f392220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e4e6174696f6e616c205075626c69632052612e2e2e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d226d4868796c66223e3c64697620636c6173733d22575a35474a66223e3c6120636c6173733d22714e394b65642220687265663d222f7365617263683f69653d5554462d3826616d703b713d4c69626572616c2b6e6577732b736f75726365732b6c69737426616d703b737469636b3d483473494141414141414141414f4f514d5a4c4d7955784b4c55724d5563684c4c5339574b4d34764c55704f4c5662497953777569574a4e724d6a4d4c7a3746794b476671322d515870786564497152433851304d6f38334b726545636a4a79697772536b36434b6b67324b633644693859565a35686e707678676c6658435a3338444375496756747a514146695a305070774141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673517a4f30424d425a3642416749454159223e3c627574746f6e20636c6173733d2244586b354d65205169394664223e3c696d6720636c6173733d22685748754a2220616c743d224172726f7722207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a323470783b6d61782d6865696768743a32347078222069643d2264696d675f31312220646174612d64656665727265643d2231223e3c2f627574746f6e3e3c64697620636c6173733d22424e65617765206a69356a706620744164384420415037576e64223e4d6f726520726573756c74733c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c64697620636c6173733d22687763223e3c64697620636c6173733d226b43725954223e3c7370616e20636c6173733d2270756e657a223e3c64697620636c6173733d22424e656177652077797277586320415037576e64223e4c69626572616c20706f6c69746963616c2077656273697465733c2f6469763e3c2f7370616e3e3c2f6469763e3c6469763e3c6469763e3c64697620636c6173733d2258646c723064223e3c64697620636c6173733d22696467386265223e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d536c6174652b4d6167617a696e6526616d703b737469636b3d483473494141414141414141414f4f5155654c557a3955334d4449724e7259776b73724a54456f74537378524b4d6a5079537a4a54416179796c4f54696a4e4c556f756a57424d724d764f4c547a4679675a5762787875565730493547626c4642656c4a70786752426b456c7a4d334b636f70796f524c47795a55705656433253564a4b5354715562576c55626c7a796931484b423666564453794d69316a35676e4d5353314956664250544536737938314942452d4b766d72674141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a3642416749454167223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f31332220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e536c617465204d6167617a696e653c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d42757a7a4665656426616d703b737469636b3d483473494141414141414141414f4f5155654c537a3955334d44637279796e4b4e5a4c4b7955784b4c55724d55536a497a386b7379557747737370546b346f7a53314b4c6f3167544b7a4c7a69303878677455626d6363626c5674434f526d3552515870536163594f6345795a735847466c414a694b6c514365506b797051714b4e736b4b61556b486371324e436f334c766e464b4f5744302d6f4746735a467242784f7056565662716d704b5144564e44327173774141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a364241674945416b223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f31352220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e42757a7a466565643c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d4461696c792b4b6f7326616d703b737469636b3d483473494141414141414141414f4f5155654c557a3955334d453675544b6b796b73724a54456f74537378524b4d6a5079537a4a54416179796c4f54696a4e4c556f756a57424d724d764f4c547a4679675a51626d6363626c5674434f526d3552515870536163597751595a6d5255625730416c7a4d334b636f70796f524a67473642736b3653556b6e516f32394b6f334c6a6b46364f554430367247316759463746797569526d356c5171654f635841774447505a396673774141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a364241674945416f223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f31372220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e4461696c79204b6f733c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c6120636c6173733d22425647304e622220687265663d222f7365617263683f69653d5554462d3826616d703b713d54616c6b696e672b506f696e74732b4d656d6f26616d703b737469636b3d483473494141414141414141414f4f5155654c557a3955334d456c4b4b556b336b73724a54456f74537378524b4d6a5079537a4a54416179796c4f54696a4e4c556f756a57424d724d764f4c547a4679675a51626d6363626c5674434f526d3552515870536163597751595a6d5255625730416c7a4d334b636f70796f524c47795a5570565641323244596f32394b6f334c6a6b46364f5544303672473167594637454b6879546d5a47666d705373453547666d6c5251722d4b626d35674d415637446f6d62304141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735173396f424d425a3642416749454173223e3c6469763e3c646976207374796c653d2277696474683a3131327078223e3c64697620636c6173733d2253374a647a6522207374796c653d2277696474683a31313270783b6865696768743a3131327078223e3c696d6720636c6173733d2245594f736c642220616c743d2222207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a31313270783b6d61782d6865696768743a3131327078222069643d2264696d675f31392220646174612d64656665727265643d2231223e3c2f6469763e3c64697620636c6173733d22525775676763206b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e54616c6b696e6720506f696e7473204d652e2e2e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d226d4868796c66223e3c64697620636c6173733d22575a35474a66223e3c6120636c6173733d22714e394b65642220687265663d222f7365617263683f69653d5554462d3826616d703b713d4c69626572616c2b706f6c69746963616c2b776562736974657326616d703b737469636b3d483473494141414141414141414f4f514d5a4c4b7955784b4c55724d55536a497a386b7379557747737370546b346f7a53314b4c6f3167544b7a4c7a6930387863756e6e366873596d6363626c5674434f526d3552515870536163594f6345795a735847466c414a63374f796e4b4a6371495278636d564b465a52746b7052536b67356c57787156473566385970547977576c314177766a496c5938386744456f63564e75514141414126616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673517a4f30424d425a3642416749454177223e3c627574746f6e20636c6173733d2244586b354d65205169394664223e3c696d6720636c6173733d22685748754a2220616c743d224172726f7722207372633d22646174613a696d6167652f6769663b6261736536342c52306c474f446c68415141424149414141502f2f2f2f2f2f2f7948354241454b414145414c414141414141424141454141414943544145414f773d3d22207374796c653d226d61782d77696474683a323470783b6d61782d6865696768743a32347078222069643d2264696d675f32312220646174612d64656665727265643d2231223e3c2f627574746f6e3e3c64697620636c6173733d22424e65617765206a69356a706620744164384420415037576e64223e4d6f726520726573756c74733c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c68723e3c64697620636c6173733d226475662d68223e3c64697620636c6173733d22664c74587363206949576d346222206f6e636c69636b3d227870287468697329223e3c64697620636c6173733d224c796d3857223e3c64697620636c6173733d2241655151756220687763223e3c2f6469763e3c64697620636c6173733d2259435537656220687763223e3c2f6469763e3c64697620636c6173733d2249795961456420687778223e3c2f6469763e3c64697620636c6173733d2245435548516520687778223e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c6120687265663d222f75726c3f713d68747470733a2f2f656e2e77696b6970656469612e6f72672f77696b692f4178696f735f28776562736974652926616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351466a4159656751494252414226616d703b7573673d414f76566177333867573437455f39394a326a683746384149573439223e3c64697620636c6173733d22424e656177652076766a774a6220415037576e64223e4178696f7320287765627369746529202d2057696b6970656469613c2f6469763e3c64697620636c6173733d22424e656177652055506d697420415037576e64223e68747470733a2f2f656e2e77696b6970656469612e6f7267202623383235303b2077696b69202623383235303b204178696f735f2877656273697465293c2f6469763e3c2f613e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d226b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c6469763e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e4178696f7320287374796c697a6564206173204158494f532920697320616e20416d65726963616e206e65777320616e6420696e666f726d6174696f6e207765627369746520666f756e64656420696e203230313620627920666f726d657220506f6c697469636f207374616666657273204a696d2056616e64654865692c204d696b6520416c6c656e2c20616e6420526f7920536368776172747a2e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e66616365626f6f6b2e636f6d2f6178696f736e6577732f26616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351466a415a656751494178414226616d703b7573673d414f76566177306e6d4a4834444273432d317679714158357832346a223e3c64697620636c6173733d22424e656177652076766a774a6220415037576e64223e4178696f73202d20486f6d65207c2046616365626f6f6b3c2f6469763e3c64697620636c6173733d22424e656177652055506d697420415037576e64223e68747470733a2f2f7777772e66616365626f6f6b2e636f6d202623383235303b202e2e2e202623383235303b204272616e64202623383235303b2057656273697465202623383235303b204e6577732026616d703b204d6564696120576562736974653c2f6469763e3c2f613e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d226b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c6469763e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e5468657265206973206e6f207761792052657075626c6963616e732063616e206368616e6765206269727468207261746573206f7220637572622074686973207472656e64202623383231323b20616e642074686572652773206e6f7420612073696e676c652064656d6f67726170686963206d6567617472656e642074686174206661766f72732052657075626c6963616e732e206178696f732e636f6d2e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c6120687265663d222f75726c3f713d68747470733a2f2f7777772e68626f2e636f6d2f6178696f7326616d703b73613d5526616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d43467351466a4161656751494168414226616d703b7573673d414f7656617730314959735a763273624653415f745778396f6a6850223e3c64697620636c6173733d22424e656177652076766a774a6220415037576e64223e4178696f73202d204f6666696369616c205765627369746520666f72207468652048424f20536572696573202d2048424f2e636f6d3c2f6469763e3c64697620636c6173733d22424e656177652055506d697420415037576e64223e68747470733a2f2f7777772e68626f2e636f6d202623383235303b206178696f733c2f6469763e3c2f613e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d226b43725954223e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e3c6469763e3c6469763e3c64697620636c6173733d22424e656177652073337639726420415037576e64223e4b6e6f776e20666f722064656c69766572696e67206e6577732c20636f7665726167652c20616e6420696e7369676874207769746820612064697374696e6374697665206272616e64206f6620736d61727420627265766974792c204178696f73206f6e2048424f2068656c707320766965776572732062657474657220756e6465727374616e642074686520626967207472656e6473a02e2e2e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c6469763e3c64697620636c6173733d225a494e62626320787064204f3967356363207555504769223e3c64697620636c6173733d226b43725954223e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e3c7370616e20636c6173733d224643557030632072514d516f64223e52656c617465642073656172636865733c2f7370616e3e3c2f6469763e3c2f7370616e3e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b6a7326616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a3642416742454145223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f73206a733c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b6a73223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b68626f26616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a3642416742454149223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f732068626f3c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b68626f223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b6d65616e696e6726616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a364241674245414d223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f73206d65616e696e673c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b6d65616e696e67223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b76756526616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a3642416742454151223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f73207675653c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b767565223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b76732b666574636826616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a3642416742454155223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f732076732066657463683c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b76732b6665746368223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b72656163742b6e617469766526616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a3642416742454159223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f73207265616374206e61746976653c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b72656163742b6e6174697665223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b74762b73686f7726616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a3642416742454163223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f732074762073686f773c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b74762b73686f77223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c64697620636c6173733d22783534677466223e3c2f6469763e3c64697620636c6173733d2258374e545665223e3c6120636c6173733d2274486d6651652220687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b63646e26616d703b73613d5826616d703b7665643d326168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735131514a3642416742454167223e3c64697620636c6173733d22616d33514266223e3c6469763e3c7370616e3e3c64697620636c6173733d22424e656177652064654976436220415037576e64223e6178696f732063646e3c2f6469763e3c2f7370616e3e3c2f6469763e3c2f6469763e3c2f613e3c64697620636c6173733d224842544d366420585337794764223e3c6120687265663d222f7365617263683f69653d5554462d3826616d703b713d6178696f732b63646e223e3c64697620636c6173733d22424e65617765206d41646a516320754565633320415037576e64223e2667743b3c2f6469763e3c2f613e3c2f6469763e3c2f6469763e3c2f6469763e3c2f6469763e3c666f6f7465723e203c6469763e20203c64697620636c6173733d225a494e62626320787064204f396735636320755550476920426d50357466223e3c64697620636c6173733d226e4d796d6566204d5578476264206c794c776c63223e3c6120636c6173733d226e424445316220473565466c662220687265663d222f7365617263683f713d6178696f7326616d703b69653d5554462d3826616d703b65693d704d524f586250624472505339414f426d4b5059425126616d703b73746172743d313026616d703b73613d4e2220617269612d6c6162656c3d224e6578742070616765223e4e657874202667743b3c2f613e3c2f6469763e3c2f6469763e203c2f6469763e2020203c6469762069643d226d436c6a6f62223e3c6469763e3c6120687265663d222f75726c3f713d68747470733a2f2f6163636f756e74732e676f6f676c652e636f6d2f536572766963654c6f67696e253346636f6e74696e7565253344687474703a2f2f7777772e676f6f676c652e636f6d2f73656172636825323533467125323533446178696f7325323532366f7125323533446178696f73253235323661717325323533446368726f6d652e302e36396935396c326a306c336a36396936302e3831316a306a372532353236736f75726365696425323533446368726f6d652532353236696525323533445554462d38253236686c253344656e26616d703b73613d5526616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d434673517873384343475126616d703b7573673d414f765661773338654a695f306b32366743644b726d315057417979223e5369676e20696e3c2f613e3c2f6469763e3c6469763e3c6120636c6173733d226b73545534632220687265663d22687474703a2f2f7777772e676f6f676c652e636f6d2f707265666572656e6365733f686c3d656e2d434126616d703b66673d3126616d703b73613d5826616d703b7665643d306168554b4577697a76597a4173766a6a4168557a4b58304b4851484d4346735135665543434755223e53657474696e67733c2f613e3c6120636c6173733d226b73545534632220687265663d222f2f7777772e676f6f676c652e636f6d2f696e746c2f656e5f63612f706f6c69636965732f707269766163792f3f66673d31223e507269766163793c2f613e3c6120636c6173733d226b73545534632220687265663d222f2f7777772e676f6f676c652e636f6d2f696e746c2f656e5f63612f706f6c69636965732f7465726d732f3f66673d31223e5465726d733c2f613e3c2f6469763e3c2f6469763e20203c2f666f6f7465723e3c736372697074206e6f6e63653d224f77774e4a497141533843544d31352f4351595531513d3d223e2866756e6374696f6e28297b76617220686c3d27656e2d4341273b2866756e6374696f6e28297b76617220623d746869737c7c73656c662c643d2f5e5b5c772b2f5f2d5d2b5b3d5d7b302c327d242f2c653d6e756c6c3b76617220663d646f63756d656e742e717565727953656c6563746f7228222e6c22292c673d646f63756d656e742e717565727953656c6563746f72282223736622292c6b3d672e717565727953656c6563746f7228222e73626322292c6c3d672e717565727953656c6563746f7228225b747970653d746578745d22292c6d3d672e717565727953656c6563746f7228225b747970653d7375626d69745d22292c6e3d672e717565727953656c6563746f7228222e736322292c703d672e717565727953656c6563746f7228222e7822292c713d6c2e76616c75652c723d5b5d2c743d2d312c753d712c772c782c793b717c7c2870262628702e7374796c652e646973706c61793d226e6f6e6522292c7a28213129293b66756e6374696f6e207a2861297b6966286b2e636c6173734c6973742e636f6e7461696e732822657362632229297b76617220633d6b2e636c6173734c6973742e636f6e7461696e732822636873626322292c683d6b2e636c6173734c6973742e636f6e7461696e73282272746c73626322293b612626286e2e7374796c652e646973706c61793d22626c6f636b222c633f28672e7374796c652e626f726465725261646975733d2232307078203230707820302030222c6e2e7374796c652e626f72646572426f74746f6d3d2231707820736f6c69642023444645314535222c6d2e7374796c652e626f726465725261646975733d683f2232307078203020302030223a223020323070782030203022293a6b2e7374796c652e626f726465725261646975733d683f22302038707820302030223a2238707820302030203022293b617c7c286e2e7374796c652e646973706c61793d226e6f6e65222c633f28672e7374796c652e626f726465725261646975733d2232307078222c6e2e7374796c652e626f72646572426f74746f6d3d226e6f6e65222c6d2e7374796c652e626f726465725261646975733d683f2232307078203020302032307078223a223020323070782032307078203022293a6b2e7374796c652e626f726465725261646975733d683f223020387078203870782030223a22387078203020302038707822297d7d66756e6374696f6e204128297b672e717565727953656c6563746f7228225b6e616d653d6f715d22292e76616c75653d753b672e717565727953656c6563746f7228225b6e616d653d6171735d22292e76616c75653d22686569726c6f6f6d2d7372702e222b28303c3d743f743a2222292b222e222b28303c722e6c656e6774683f22306c222b722e6c656e6774683a2222297d0a66756e6374696f6e204328297b773d6e756c6c3b69662878297b76617220613d222f636f6d706c6574652f7365617263683f636c69656e743d686569726c6f6f6d2d73727026686c3d222b686c2b22266a736f6e3d742663616c6c6261636b3d685326713d222b656e636f6465555249436f6d706f6e656e742878293b22756e646566696e656422213d3d747970656f6620647326266473262628612b3d222664733d222b6473293b76617220633d646f63756d656e742e637265617465456c656d656e74282273637269707422293b632e7372633d613b6966286e756c6c3d3d3d6529613a7b613d622e646f63756d656e743b69662828613d612e717565727953656c6563746f722626612e717565727953656c6563746f7228227363726970745b6e6f6e63655d222929262628613d612e6e6f6e63657c7c612e67657441747472696275746528226e6f6e63652229292626642e74657374286129297b653d613b627265616b20617d653d22227d28613d65292626632e73657441747472696275746528226e6f6e6365222c61293b646f63756d656e742e626f64792e617070656e644368696c642863293b783d6e756c6c3b773d73657454696d656f757428432c353030297d7d0a66756e6374696f6e204428297b666f72283b6e2e66697273744368696c643b296e2e72656d6f76654368696c64286e2e66697273744368696c64293b723d5b5d3b743d2d313b7a282131297d66756e6374696f6e204528297b76617220613d6e2e717565727953656c6563746f7228222e73637322293b61262628612e636c6173734e616d653d2222293b303c3d743f28613d6e2e6368696c644e6f6465735b745d2c612e636c6173734e616d653d22736373222c713d612e74657874436f6e74656e74293a713d753b6c2e76616c75653d717d6c2e6164644576656e744c697374656e65722822666f637573222c66756e6374696f6e28297b66262628662e7374796c652e646973706c61793d226e6f6e6522297d2c2131293b6c2e6164644576656e744c697374656e65722822626c7572222c66756e6374696f6e28297b4428293b66262628662e7374796c652e646973706c61793d2222297d2c2131293b6c2e6164644576656e744c697374656e657228226b65797570222c66756e6374696f6e2861297b713d6c2e76616c75653b793d21313b31333d3d612e77686963683f4128293a32373d3d612e77686963683f284428292c66262628662e7374796c652e646973706c61793d2222292c713d752c6c2e76616c75653d71293a34303d3d612e77686963683f28742b2b2c743e3d722e6c656e677468262628743d2d31292c452829293a33383d3d612e77686963683f28742d2d2c2d313e74262628743d722e6c656e6774682d31292c452829293a28613d71293f2870262628702e7374796c652e646973706c61793d2222292c783d612c777c7c4328292c753d61293a2870262628702e7374796c652e646973706c61793d226e6f6e6522292c7a282131292c4428292c753d22222c793d2130297d2c2131293b6d2e6164644576656e744c697374656e65722822636c69636b222c412c2131293b702e6164644576656e744c697374656e65722822636c69636b222c66756e6374696f6e28297b6c2e76616c75653d22223b702e7374796c652e646973706c61793d226e6f6e65223b7a282131297d2c2131293b6b2e6164644576656e744c697374656e65722822636c69636b222c66756e6374696f6e28297b6c2e666f63757328297d2c2131293b77696e646f772e68533d66756e6374696f6e2861297b6966282179297b4428293b303d3d615b315d2e6c656e67746826267a282131293b666f722876617220633d303b633c615b315d2e6c656e6774683b632b2b297b76617220683d615b315d5b635d5b305d2c763d646f63756d656e742e637265617465456c656d656e74282264697622293b762e696e6e657248544d4c3d683b762e6164644576656e744c697374656e657228226d6f757365646f776e222c66756e6374696f6e2842297b422e70726576656e7444656661756c7428293b72657475726e21317d2c2131293b683d682e7265706c616365282f3c5c2f3f623e2f672c2222293b762e6164644576656e744c697374656e65722822636c69636b222c66756e6374696f6e2842297b72657475726e2066756e6374696f6e28297b743d423b4128293b4528293b4428293b672e7375626d697428297d7d2863292c2131293b6e2e617070656e644368696c642876293b7a282130293b722e707573682868297d7d7d3b7d292e63616c6c2874686973293b7d2928293b2866756e6374696f6e28297b66756e6374696f6e20622861297b666f7228613d612e7461726765747c7c612e737263456c656d656e743b612626224122213d612e6e6f64654e616d653b29613d612e706172656e74456c656d656e743b61262628612e687265667c7c2222292e6d61746368282f5c2f7365617263682e2a5b3f265d74626d3d697363682f29262628612e687265662b3d22266269773d222b646f63756d656e742e646f63756d656e74456c656d656e742e636c69656e7457696474682c612e687265662b3d22266269683d222b646f63756d656e742e646f63756d656e74456c656d656e742e636c69656e74486569676874297d646f63756d656e742e6164644576656e744c697374656e65722822636c69636b222c622c2131293b646f63756d656e742e6164644576656e744c697374656e65722822746f7563685374617274222c622c2131293b7d292e63616c6c2874686973293b3c2f7363726970743e3c2f6469763e3c212d2d206363746c636d2035206363746c636d202d2d3e3c746578746172656120636c6173733d2263736922206e616d653d2263736922207374796c653d22646973706c61793a6e6f6e65223e3c2f74657874617265613e3c736372697074206e6f6e63653d224f77774e4a497141533843544d31352f4351595531513d3d223e2866756e6374696f6e28297b76617220653d27704d524f586250624472505339414f426d4b50594251273b76617220736e3d27776562273b2866756e6374696f6e28297b76617220713d66756e6374696f6e2861297b76617220633d77696e646f772c643d646f63756d656e743b69662821617c7c226e6f6e65223d3d612e7374796c652e646973706c61792972657475726e20303b696628642e64656661756c74566965772626642e64656661756c74566965772e676574436f6d70757465645374796c65297b76617220623d642e64656661756c74566965772e676574436f6d70757465645374796c652861293b696628622626282268696464656e223d3d622e7669736962696c6974797c7c22307078223d3d622e686569676874262622307078223d3d622e7769647468292972657475726e20307d69662821612e676574426f756e64696e67436c69656e74526563742972657475726e20313b76617220663d612e676574426f756e64696e67436c69656e745265637428293b613d662e6c6566742b632e70616765584f66667365743b623d662e746f702b632e70616765594f66667365743b766172206b3d662e77696474683b663d662e6865696768743b72657475726e20303e3d662626303e3d6b3f303a303e622b663f323a623e3d28632e696e6e65724865696768747c7c642e646f63756d656e74456c656d656e742e636c69656e74486569676874293f333a303e612b6b7c7c613e3d28632e696e6e657257696474687c7c642e646f63756d656e74456c656d656e742e636c69656e745769647468293f343a317d3b76617220723d652c763d736e3b66756e6374696f6e207728612c632c64297b613d222f67656e5f3230343f617479703d63736926733d222b28767c7c2277656222292b2226743d222b612b2822266c6974653d312665693d222b722b2226636f6e6e3d222b2877696e646f772e6e6176696761746f72262677696e646f772e6e6176696761746f722e636f6e6e656374696f6e3f77696e646f772e6e6176696761746f722e636f6e6e656374696f6e2e747970653a2d31292b63293b633d222672743d223b666f7228766172206220696e206429612b3d632b622b222e222b645b625d2c633d222c223b72657475726e20617d66756e6374696f6e20782861297b613d7b7072743a617d3b77696e646f772e77737274262628612e777372743d77696e646f772e77737274293b72657475726e20617d66756e6374696f6e20792861297b77696e646f772e70696e673f77696e646f772e70696e672861293a286e657720496d616765292e7372633d617d0a2866756e6374696f6e28297b666f722876617220613d2b6e657720446174652d77696e646f772e73746172742c633d782861292c643d302c623d302c663d302c6b3d646f63756d656e742e676574456c656d656e747342795461674e616d652822696d6722292c6d3d2226696d6e3d222b6b2e6c656e6774682b22266269773d222b77696e646f772e696e6e657257696474682b22266269683d222b77696e646f772e696e6e65724865696768742c413d66756e6374696f6e28682c7a297b682e6f6e6c6f61643d66756e6374696f6e28297b623d2b6e657720446174652d77696e646f772e73746172743b7a26262b2b6e3d3d66262628643d622c742829293b682e6f6e6c6f61643d6e756c6c7d7d2c743d66756e6374696f6e28297b6d2b3d2226696d613d222b663b632e6166743d643b7928772822616674222c6d2c6329297d2c6e3d302c423d302c673d766f696420303b673d6b5b422b2b5d3b297b766172206c3d313d3d712867293b6c26262b2b663b76617220753d672e636f6d706c657465262621672e6765744174747269627574652822646174612d646566657272656422292c703d7526264e756d62657228672e6765744174747269627574652822646174612d696d6c2229297c7c303b752626703f286c26262b2b6e2c70262628673d702d77696e646f772e73746172742c6c262628643d4d6174682e6d617828642c6729292c623d4d6174682e6d617828622c672929293a4128672c6c297d647c7c28643d61293b627c7c28623d64293b6e3d3d6626267428293b77696e646f772e6164644576656e744c697374656e657228226c6f6164222c66756e6374696f6e28297b77696e646f772e73657454696d656f75742866756e6374696f6e28297b632e6f6c3d2b6e657720446174652d77696e646f772e73746172743b632e696d6c3d623b76617220683d77696e646f772e706572666f726d616e6365262677696e646f772e706572666f726d616e63652e74696d696e673b68262628632e727173743d682e726573706f6e7365456e642d682e7265717565737453746172742c632e727370743d682e726573706f6e7365456e642d682e726573706f6e73655374617274293b7928772822616c6c222c6d2c6329297d2c30297d2c2131297d2928293b7d292e63616c6c2874686973293b7d2928293b3c2f7363726970743e3c736372697074206e6f6e63653d224f77774e4a497141533843544d31352f4351595531513d3d223e66756e6374696f6e205f736574496d6167657353726328652c63297b66756e6374696f6e20662861297b612e6f6e6572726f723d66756e6374696f6e28297b612e7374796c652e646973706c61793d226e6f6e65227d3b612e7372633d637d666f722876617220673d302c623d766f696420303b623d655b672b2b5d3b297b76617220643d646f63756d656e742e676574456c656d656e74427949642862293b643f662864293a2877696e646f772e676f6f676c653d77696e646f772e676f6f676c657c7c7b7d2c676f6f676c652e6969723d676f6f676c652e6969727c7c7b7d2c676f6f676c652e6969725b625d3d63297d7d3b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f6a7065673b6261736536342c2f396a2f34414151536b5a4a5267414241514141415141424141442f3277434541416b474277674842676b494277674b43676b4c445259504451774d445273554652415749423069496941644878386b4b4451734a4359784a7838664c5430744d5455334f6a6f364979732f52443834517a51354f6a634243676f4b4451774e47673850476a636c487955334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e7a63334e2f2f4141424549414367414b414d4249674143455145444551482f7841415a4141414441514542414141414141414141414141414141414267634243414c2f784141734541414241774d434241554541774141414141414141414241674d454141555242694548456a4642453147426b61456959634852464256782f3851414751454141674d424141414141414141414141414141414141514944424155412f385141485245414177454141674d424141414141414141414141414141454341784a4242424d7842662f61414177444151414345514d5241443841754e655843416e4a3644725735706631746450362b794f6870584b2b2b5044622b3265703971664f48644b56325236364c4b4862364632343858744c57366139456b6d64346a4b696c5254487944397763394b66597237556d4d3149595746744f6f43304c4232556b6a4949394b3566313559333151303374686e4d6474774d504c386c455a542b765565645666675271495858536f746a7a67564a7468434d5a33384d377039747836553232667274794a342b767479566c506f7242306f71496e4d5053705a7275354766656a486279576f333044486452362f67565272784a6369323139356c70546a6752394345444a4a376256504e4c574b5a4c76726239776a504962625558564b6451527a4b37664e58764334787931726f797630656438635a58305a4470526d586f682b7879414f61537965645248527737672b687837564265476c346530667841615a6d6b7474716456436d4950626647663841516f44357271554441785541343161496e6a564b62725a59456951334f547a752f7741644255554f4a786b37644d6a487a564f716455365a705a776f68536a6f45644b4b576548647a6e584c53634a64316a5078357a4b664265513867704a4b647562667a4739464b4f4d7441414859555555414734464742525252434742355555555678782f2f32515c7833645c783364273b76617220693d5b2764696d675f31275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414945414141424943414d414141446d6d575965414141416f6c424d5645556949694c2f2f2f38414141415a47526b644852337537753751304e41534568494b6d654156465256675947414f4467352b666e3757317462382f507a643364314c533073784d5447506a342b656e703675727134714b69705a57566d30744c544477384e766232382f507a2f303950546a342b4f38764c783364336547686f596a484255556572416545414137676138416d657133312f496a46674d586135704570654d416b64375336506f61585952666b376f4f6a63346453474d476e2b6f564141416a445141664f6b77684c6a636b4141416256485a4d6b39507941414143636b6c455156526f6765325732334c694d41794748646b4a635135415349434568435464412b774a324c624c2b372f61536e4b67334f4f5a6e646e526431484c78575039746e384a6c42494551524145515241455152442b41347a57326a794d48476a3661397a6e4d3044306662314e30356e784b7142496b6d53706c55356f744e4d2f6d7655475a355149564c614e366e49444c6d74716d374c4d647542527761665051524345566b474634787a504452454743326944494d633855474a414c48597036656e647450497034637458334447624b656877334550615577616c777943495145456533476c5342634e747376636d3458434d762b47474853697a6f353033674b6d4441517772674a7254686537675331764d6359676958756a4c43324d63662f394247326f464b787a7a4d7544444f77554e35616f4c6f2f6f35793654374b5145324e4b536542507938785045766c395135674e78677a4b5341336d434c486a5441463751636e414b6f4275767246635a54484d66734144532b556650626730384b5345374231773137537235685439617235554e3150696e6766496b764c37387a50697157576e4d33475374594f3463517475645046704d5238384b54684665386773766261454e33566e594350346854554842644f67556b726f5a37626379564879652b6f51744f72316a30624331584456514a44362f51756c54634c6b704949616c797168612b732b6435666345724f492f4b724e45414c62416670764d354252326e785a57574c5a4c73686d67414d4d6e48367a7a4a4f39727739416344324f4b6550643345766e2b73526d35417051565930726c4436685768776c7277706542775242396552347a342f6b4f36434e634673395170534e6c355965344d6d5042547446585a6565764c65415778693236746f48646d6d4538393052547452316465676448686255493934326d7747313075787750486d6973647a613563515554544e354f355351766168507870706e4c73646a3671635478667239663361574c364c4d75474e5956416b634a3551316c67557932365254335947532f4457716a7262652b704934334966574a6e6941733577726e4c6f6748774e387273746b3754377858724a623867434949674349496743494c77442f6b4c6b327368546d377038716741414141415355564f524b35435949495c783364273b76617220693d5b2764696d675f3233275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414841414141427743414d414141447850675235414141416231424d5645584d4141442f2f2f2f4b4141444c4141442b2b2f763435655879304e44514e7a666f72713754516b4c72744c54696b5a484d4a53584b44772f34346548484141446f7036663939766235367572383866483031396676784d54676834664f49694c61626d37636433666b6d4a6a78793876575631666e6f4b445453456a55546b3759596d4c4e4768726666332f514d444475766231676d3047614141414758306c455156526f6762576234614b714b68434662636a4b4c44484e637165563758722f5a37774774484d424b70347238394f5154324341575441464732584868635757595855706e337661373571345341363249734e325349713432584861503875344374385642467a5a31564a36382f50675243774941736149732f4b6154634e6c61636d346546395538506a5a484261424d6b714e316d31755442622b4b30507265446d68645a63316f2b373762585852706864346a446a51354376385562676969783033336d387236414e7548695a4f764c452f4f65475746374a5830414d383952656e327346356c67333176573844486b365733756751387a46653367785659414b76773539487a566a37546d507451324131396e6e384d6779386a725950674e6c75654c686265367947654b45447277754d4866716a48484b636376534441626a614f78546e6d333765316f585841526f443369354b6e477654684c4665337145304b6e67766e58385039394c3442376a55696a4d57586174566358706f79317a762f452f3043756965726c61766e346436486b704c506e4e7269794e4f74304a3953486f4734726e50627a51586f4470524c592f6c34715758787736682b727442464d38756b637165525256396e48362b377255395734444c573763384f3364724c634364714c44796374776736753576477a4b4279647053616162325876416e74724d3273514967443858444d4a466c617a4c47734470333637794c702b6b39716f2f79363646544c58753261735633524d537a2b4261565969535037592f616a722f64473156754f574e734a346862614f4c5446705a63756b5834567644614375695734612b6665516856306e74364c392f393041372b7531507942707234612b6c5563464b7133674d6933494c452b68762b6a64676645476f5577456738496a454e30472b3470596b4958416d497245414f6a304d4c4a5a44747865386c64756f556f4278505a3241673136495170366b5265765544412f3661426d5272345767704e44454b3359464d5241727577486235464835547779676253326f2f55506252424744417a4b6b52554f494f4446672b445569524b464a446b667345494a585467476f65342f354457336467384b696d41646b394d2b70557a39794172446c4d416759557638746b734b507732423359397047786c6734436c593973594a506d6f5475513751496c44394f6a47314175463276636974324241656e37345168512b6367524e3732584f394459674d654163726e4157423745787478413556304a646d7271455269516d41596f48336a75452f676a436d4b3038657352474f78465a495842307237774347534e384a73664b4e6b632f41454445744e673159337631444c72436368494e43666d38477a704436693233514d63644644734561695544473746753556486f4654417141537057666f444b744652595a5261654151714266794c3673596a554a3263354f673346342f4134476d4a55742f507641476c6b74485554656b524b4a58526f734374654f5552794e6269425467325a48655051435658387a31757852642f514b566b674d42714f506561462f694a55753851382b424a78377a41674552732b656f2f444a77644b4b505565793978426941396f454b706276712f3650384464316563427161366d526e347a4c4842596d7145634877316377767871456b706d5733506f664d73774d5664587a34586d72715a47336a456f4e73537063344d314134564c65706d6271424e795352506a304474356b5a47716462626e4c6d416d674b5736755a70385a765a674158347a554e737862596c6454626745734931715752513363774d58427a4252355477683256325a714332664d717a31462b6a69544d434678685a53415673584f504f436453556a4e694b4e336f5435775271536b61637752386172596c7a416e55464c4657785436436d5a4f5235757a5931484941702b4d4967634d6b4d4a625049344a6b445542755a51534436694f304d33674749303273456d454f594c34562f6a74757a425a68324c483571517a414d314f3970684d6948726467473542335438794847674f676a37436165645a74744177375a4b44416a773866674c4856326f4f59335574313074754c3567526e653030683138333168667544696851366852616b65674b69413154334e3766504d4278447661526775715436416d674a4764654d46695063303671723336524734534347456b3165393667782b4b6c43635347493269415834395248426b506c4c612f6f486f4c7a4777767966737757493236343862792f454d3345496b4a783731314c4e784257734a6c335746694157495855477a786e6a49764249554479477653595073554f494a5042693632505961536f314a3637726b336c6b79347a304673304f6d4f4245396e77767a572f6b6c554b75726d74776f6f34416c31712b45612f7335664463464c6f427732506a4f67397871316f54373953547449634b5746374179797069644645654a4c3157706555443239664b335a37304d6b334a554b507550784d74556d58314a776e4c596b5941454e6853495a536875676b5933654c5836334c5455305a356f566335624e594d476d6b587266506679576e4746374d6f6d38537a547772567158754839615231386d6c4150512b696177574e767339752b5351672f78336757525777586b47786d414a5561553239466f356c62516f56366335546556674474724b6b4d5864355a5434467950686754716d777a5a446a55435136794a56483535354644657a566b7a76397275416d46796e48356c467035434c5a652f566d397878474a3756477565484f71577565666e62694669522f76443672367a694d7a6c45364957462b6357783257696f7165386266377833613856746a55586c7847547845786e64362f366d674e5349656c476c334f4437704c5262626271746a6b76334466783765365a7176754978327a3374357262522f54667748444f747262744e7643415541414141415355564f524b35435949495c783364273b76617220693d5b2764696d675f33275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414841414141427743414d414141447850675235414141416456424d5645584e4742332f2f2f2f4a4141444d414144373850444d44685856556c4c4d4642726e73612f37382f505556316a76794d62504953583032747276797372464141446a6b35585150554c4d42512f787a39444d41416a36362b765351304c2b2b76726e724b7a32354f5471754c6661654866585a4754737672375a6348445961576e6669597664666f48504d54446a6f364854536b765350446e6a6d70765a704856754141414461306c455156526f67653258323361714d42424177345159735642705141533543496a2b2f79636553494a415346307439727a4e6672484e5a546245535449536769414967694149676941496769414973676b6d597141514338614e446a3567616541324c4d4f48475749646e43625876413772706a6f5145504f78784f395a7a423461664b342b5448547a776769304c664a4742743852594c6f314f6a6b4b4c3768387a5a5277433370536d466169692f71474c49597957484d5a65353947547545615a57636433413175766c59366337496948683845396b5044783079343834615747414a6e545342554c7832466774646e5938776c595376685949692f46626f7668653563534c6c6c56456774517363723645736832495673496153352b58707a59526e6d6564366b6d57342b4a2f53566b454a6d695a5574684841596661653036594f48355578344261423938736230324f37564d4f386758676e4a555350484a7570766d43397058476a6635533644437771515a3072495366794d4b4b4251442b2f434b2b4759467141656a7131377756566871696e704f6656444b61526b546e794d354e413966555049495657724449766748496f6d4a72526343416e7a737a484d5a69487235462b6e75316a47376e644b502b70734e677048657a594c395174326c4b7942306c6b3133527731636276514d535a4f634f7173684379525231314e7477726a5175367473583042726464434168394b7446564935596f477134746a6a4c312b696c784f3844634c5a614b6e31685674413475513357574f4a5275467a4a636e5832564c476461644c4549563646787346616f63574f304a4f6162772f6c346f4f696b6b7470775277354833763453326e486b6c394137764c656d5864556b50746955563356386b5457354e6d7353574e46444c6262483561465062596d2f64467235745736674a356561545268326c67572f37456d6c704f646f365479334a357150744d4877366865314c48413456737731554e66444f625347664f4c4f7436544448664962453053665464754644686d6a57526e6c314c5a766952463265795273584d4c2f4c532f78636d5562654d706134792f654c56526e2b654b6645494e4449494e357559525351317054514734466e4f674856785849774c364b456876326969464a35344e7734544c70447049756f422b744c4f646f586370422f716e48756e5432466e2b32587068552f466e4b69612b5567684b4d4d666b777535366b517a737048474b625065746f564d714f5663474950507859534e6c586e575271476a314c566a665a5350306a55446a4b46743138492b2b71764e4f4e2b49335276342b2b3474345345482b755459394c454b3245354658656d4d41586a42356b7058505953656a63435a4a5536376937425365616d6d333355414e4f39435858304f534f7149536d48542f45554476316c4e77714e3371485568766f6a6b2b2f746e614a39432f72484b764375714b7271756d75424c6737634958466e304434564271597838742f6e453571394b735239642b324446776d647651706e61702f5a6a7665335563475a72654241454152424541524245415242454154354466384152776f2b4948597561487741414141415355564f524b35435949495c783364273b76617220693d5b2764696d675f35275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414555414141424643414d414141417255397362414141415946424d564555414141442f2f2f2b4b696f724f7a733553556c4c613274716e70366672362b7648783866382f507a7a382f4e61576c712b7672342b506a373239765a7763484376723639396658316b5a47524d544579676f4b41774d44415044772b31746257616d706f6749434156465255714b6971546b354e455245527061576b354f546d4d703246774141414239456c455156525968613257366136434d4243465757565241554655334f3737762b55464539716d6338704d577336764575424c4d3265324b4e354455534c53536631775171386a6d5470467959522f494f574b4d7670446e73554b4b57372b6c4c4f3653756f506958704675516451486f7079436143552b317255424667304e53736c44376a4b50686131697649496f4f6a3862774d6f2b31696b382f2f7044376d70713452594e4f6a6d456b424a464157336f3965677a3333716b67704c2f4957554d74626e613879712b6f4f55334b436b504b57476b4c6b2b39455042553742466333336f454e55383551417072554535386843485258654430764951522f366e4275584151796f4969577144346d39526246427946754c492f3936676a41304c69612b51636a556f47513978744b695451526d366736334b707277677054446a516a5353752b447633473857585777497a7639736d2f4b774b5868456a2b30734e3657304b572f3374323652477666702f792b5351443558365730497a6e3947695530706653696454656c384b4b51366662616f4737486f374548356b43727947644666472b49316f742b37574554794837636f527353696e76306c79596c4963486d4c79505770696f6d6c43495972586c476e2f766a54373447484f4b624932672b587336542f347847394474526663415555624e487170456e636b474f4c576f764e4a47366f674a447a6d672f4c67324334596f745550357a5049386b774b707a2f4b71435262502f424c55706c7139436934365a46432b584f513271592f35504b2b5569306f6d4b4c50757239334745465659547a58776430336c7745467545527259654e7a434b38464f6946456d775956425675555471676f686256514d696b5034674769555766444d6859746951646974632f7555735465446a676a616741414141415355564f524b35435949495c783364273b76617220693d5b2764696d675f37275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414841414141427743414d414141447850675235414141417146424d56455541414141795a737a2f4d77372f2f2f2f304e42466246776f70544a497a614e424f546b374c793873725973744f6564497156616f735a644258614a442f4141442f4c77436d57452f4e4b51757071616e2f61463956664e4c4d31753854574d6b2b506a372f7a4d725330744b4868346663334e79337437664377734a3565586d546b355072362b76464b51314b45676767505855794d6a4b656e7035746257332f3239556348427a2b764c622f66572f2f6548442b7070372b366555724b7973524552466a5932502f5755784663744259574668634477426c5555395759486b6636744c75414141427a456c455156526f67653359363036444d42694134627036574f64685536596955416271594e4e4e6e4b4c652f35324a5543627041637570662f7a65784d515136324f686443424345415242454152424550516675314a302f3944686c7a36654b554d6a525964484863446a4532564467516571414151515141414e67444e6a344351506a615a7955674358646c365366657453516f676678633341367a794556334f704b49414f7a725057464f2f7a466d746463484a614445485a31354e736b674a496967472b673674353478596758756e4d6b474235555173776d324e37454339616750685a4f4b6e36494259587a392b6765464a72514e393141387572584d6357494e37775531534374447751684d6f70616f4262585a442b486c6d57496d6b437672427a796938624657685644746c4d444e4d47344a62396c573141564f34422f454b74417a64647741524c447634427a727541355172674c2b4a77494c7335516d4f677a30596241796d41413131446334754762545838396a333466656962416c3032324455466c7338334f304f677863593672326241674133464166656a76594c59693232453176624f33582f67383364687a324232317a6d4f56336d6b6b5479323951787943536430594a434b337042674b486b4d48684b6b306c654c57764474726b67586a464c693565737a3942782b67366b4633322f79304778614e4e4945732f737753654d343371574a696c4f384839376d36623979537a66764a6d4152674141434347414e474a47663150746e3732435461762b62714f716a43336975444632712b687933372b74435759647051424145515241455152436b33546552454559374c6f71374d6741414141424a52553545726b4a6767675c7833645c783364273b76617220693d5b2764696d675f39275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414267414141415941674d414141436447645672414141414446424d5645564d615846436866524368665243686654307443505a4141414141335253546c4d4167464a456b47784e414141414c306c455156523441575041447867647742543342544446394155697568644336574e4b2f762f2f2f792b55676772436c53413037455756676c6d4546774141356559534578654377696741414141415355564f524b35435949495c783364273b76617220693d5b2764696d675f3131272c2764696d675f3231275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414649414141425343414d4141414477386e4f70414141415946424d5645582f2f2f3933476b6c3247556a6d3074794b4e324a39496c43494e46393548557a31376648372b5071424b46584f714c7a392b2f796556337672322b547a36652f446c61336276383678655a6152516d75565358436a59594c77342b7135686143454c6c716f615972486e62505875636d2b6a71657262343751726344697939636a4536764241414144756b6c455156525968653259323961794b6853474253564555544655464866336635632f696943536c562b4e64624a4737306b6a6841636d7a41306142442f39394e4e502f325052754e434b36616345767948764d7130752f7778597a6a744c2f34513371425746487848374b72562f73746b6777614c506b48534b3049344531667731556847686939544d6235416b5662766d496d4753666f636b556f31316b62426c38566449496c7331314558654a416d2b51524c5772754d63354b526439464d6b59516e306b5a76546634714d4d7742385a504157535568634e49304b56584953713339486b727958517356715656565a5051705a357551724a4733536a69634951794f4d576a354f78546262784a53475349384475474e61386755796c686b43436752324c662b5362467058576c52346b5832474e3758506b666d59484841574331717865484a546e5478556a354f6e794c7a475a3043396277767a7a38696965307063686b6e365a79526c79424b6833696667626750506734622f44526c794f7835463958325154497856757739454d696a71534f6d4754464d626166467a4a4758324a4b4d68584632636b714c5039715633684f616855726d5a44394551626a7048787257317348644370686e4e544a446e76717450723130394e33596a6565686c323245372b38673330564f3257377a775937326b6432774f6f6677724d754e61776774706153782f6853526e534a4a764b72795a552f5157535949317543376d53784b4f3731665a424568634b68516b7a7675686a717758506b655767537151394356534f5754597933764745777a322b486d4f6c4d486d5975644930737970364b70494a553177544574506b56536f7177456536436d53354f6c59335235676235426b564568594e536449476772755a2b424c794870424a76306a4d7062384c4c764261386a566367385a6938514871765769396e595243556269752f7141344a4547634d493756724b337271365273493739674c7a74524c6a517370475644626b53506152626b566c78524d616473385132593331596248483748726b3630534e79746f7545714376644634494c4d533431306a4f63375847337051416a2b52355a726b6a76654d686f7265364f784743346b6a62555879794f546d544c78455032337964376c64793267533679794179796e59394957796865707542742b3836525a71425a774f367572354771674236527074755755586269644873616b4b72726a6f544a736d47483437467543626d7a7a4a6a5a63465449336b50434b6a54544131415848704b4b2f5972484a33336d744f68724e2b794e6e5935466e4d3135735742687443376b344a64393469776e45326d355a48576e446577466e745237473235357661547a684433576e6959376844684b45504c7a4d4759615365394f493453334a554f63565568716738523042623677324459756462747531352f6745626d376b53396b64376b6a767176613063455a4d706a504c342b524d49652b567066566375636d2b684a4a792b7168546b436339633439793768584d54713332794e53767849413078694f4c58424e676a67532b584c412b72304249476263734243746d663641624d52646139674d436b672f526e627263464b4a6d5377426444656162474352307654554a3237626a5a796d63424c7256355436727371454a6c44623778437171756459582f7a596f74346634394f58783865656139645050776e39394e4e502f34332b41627851504c6a33385835594141414141456c46546b5375516d4343273b76617220693d5b2764696d675f3133275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414841414141427743414d41414144785067523541414141636c424d564558754d794c2f2f2f2f74494144744a513334747250754d522f74496762306a346a326f3537744b5250744667442b2b666a326e706a796533502b39765838337433754c5272764f536a765244627550433334757258765545583936756e316c354c734141443731644c3372367636794d587861562f30683444367a63727858564c796347667857457a76537a2f35774c3378596c6a796447355a6e6968724141414745456c455156526f67653261375a616a4b424347465270554e49716d51324b4d332b6e37763855315569674b5a6e706d31396b357537352f2b675331486f47694b4d70326e454f4844683036644f6a51763667414c555578333564583970384c3362324f426a7343306164724b446b6873682f7777775336626848735272514433527a395a714162375558634173624f546f4f71674c6e3030534a577848496e563158413634324e776a3041377a754e71514a475750346d5349436a4d6b4a6677765051347247424f774531684231754e744c76414231306c773039612f315271627156583866664433727944586e3461546236747069314366545a616677626e6c555873657837644c50456970795646732b7a64644563306b51324e45674334786d5977477a2f493844726252783168487a3557374267583241566a614e2b682f36355462414e74437a646e7765754644486e4454434d6c6454394869766e786841612b2b3843512f48466e4733676241592f775061647a52594a675957564d46757773674772376e58724779435234686d597a6c2b4c46526f4a79313134304c715432346530542f6d506530696f4d70317070686c346e6476674e65766448496f7a2f7947517759703148357070656f4c474b31756a6c6b42526a6371564779546f5230436b2f4e4c546f69362f774b7757477a776a654163656d436e5a6532445167756c65343545415a6c56737067784770474641544735663734416b5662366f6d326146624179377a645450694b57457931454e7337644156494844364b5a5a424d507a5a5863594b3942526f394b73675651445472366f6d38594e4e50706245326a74495947352b57706764496936494e2f4577356f7636716b507a38446a716a65384751692b4e3832683237547937776c65686176667765434c594c725148515a47774256763036485a53386445483538564c2b7a4f7371733534764a4e594a4f6f4f6546674f694663785a64686c4e574b767543356352736f45716b7048417547495a44305a34596f493272664967524233684d323355574a6f437338654d326d7876514e304642457356725a636546485062695336794f734273454e703775465535754e626d745a484674414d517957476a6c646354614e37614b5a322f62446e7743477a79457444657251754843697a68354155592b75695a3969325236576731762b54614435644a686348556937672f4f6e33736d71485265394252682b47386766336c4c6c5634766f354e4345645634685242774c6b5565315843444749362b6e5347733258732b3268634878537348797451686d2b4a786c35344252767658496f4f452b55373938487470617849634f2f5235685a74567535536a734a315a39375558633269333271696e386a34476e2f3177502b334b3574625237526573705457544c6e555775437349483264446a685639614f57617150346b6a31443261356e6c6d36343074594b52746d73636c6d4f494430594d47665664743351515364726d4c4d55754e63382f5279774d42505658696c586d4534764d686b53544c5a2f582b56376f64716261415161706e4d364b634377536f3062504871734d6a634a6e667858317472536873413347397974632b564a366a4b6b655437596161774e634447314f385067484c7542303854515079334b4a4b6362504352324144756b6e36746f7152464570567a6164366944746b61387255654a536654684375454f6f6563535932344d617832374c7748787a4279546c76686b51764b36586c49636d666177623353307253467037395241434d723145552b644d78704c6564453231412f46536a474c7a794e5a724b5939516471544e384f446a6e717a44453543485a37626745436f516f5259796f3038377a65366e2b4138784f70537334326f717067314f684241373150674d67684176326b4a597153786e62426954796b466a53715a6f6c5061566863676146616963384857394e71414c434b366f536743564b32594279524d504f5355466348756d6a6d79786446456864534e4534326d4732416849755454586d6d453666456536544d706943574a4d4533615137684e6f464f63623165516c30626e4c614c51467a64635a2f69526c4c445a52547358486c7441624367662f446e45524c704448584e696a68573841795851484278766541314e38774b394157304673447764463973795a734151627962426d577035556171506a6e7a66704b6c36336e554d36365a2b594e4675425541574c4255687a4f766d4934524b36757249415949734a6c323073314949477053725449784d645868524c716f6b4936426b7979424b7276536647626861383773504b612b3030744b33595a69336845726e4d337270556c7a737258755a726f6b5959674469556c7978526167535144582b6a54327a4234394f5a4559546a577464566d474a654d3451437a573132356e327747336c366258486546636c5a6f32793673477a425664632b77754a616c583732736a5846782b6b5467697274586576667831394235745433705879324743375a553037376a30386f314e4e5a4a6739713845472f7368354731696d6b48456d34533551524e33306230563845326f4765766d6d376b4e4d52592f5155555866426c5659454c5332784a4d61724f2b726c794145497063753151424c57465a7252345476394c77476d70495957665973334e704a4a3769376279524e4c566f7a4c446f54684b7934387153664c4362357a4638356932555a4550562f7072693255326c7a346d3156324b4e33474f537472742b587a7753686b6f4d6a4e706a7448347458482b527847753653676948547030364e436851332b492f674a2f6948506855784b5a387741414141424a52553545726b4a6767675c7833645c783364273b76617220693d5b2764696d675f3135275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414841414141427743414d414141447850675235414141413156424d5645582f2f2f2f6a6641446a6567442b2f2f33696467442b2f6672686367447267514438367450366a774436376433392f5066376c41447468674431696744326a67443335395037394f763738655437307148706a525866625144716b437a3133735432343833686677446d6b555838324c50366f4450316c532f766d5437726b68623030375478794a767477492f737549626967436636776f58346e682f76304b726c6a546a6f6744583236642f77785a37686642726e706d3369677833747670546c6e316e6c70574c7172336a767a4c54706c32546e6a457a7670337a757771586f754a6278313862696e573770716f44706c46726b63786e707359336b6479626d6d555030723176347a4a4c367533583272453331707a6e796641416c4e7748774141414e4f6b6c455156526f67653161435865627568494749555143735931494d4574365578616e45474e6a77456e644e4c6b70625872372f332f536d7846657744464f2b3972653838343731546c3144466f2b7a66624e534b346b2f576c2f6d6954706a764c76417159732f6e634259325a6e36722b45705a704f6b6b526c3551572f46555a426d366e635361496f4468304c704d75383548666953524a33677a444b484175526c314f4657776c3166694f674730616861786e71536a47347362793965372f696269616276774e4b6b525365784a4659652f7268357537526c5054567a6430484b6244693334496f57566b5563496c7a2b4d72763339383833457553385948666378377965577239636a676c695630442f6b342f6d737370574f2b653636725264446d426c4e37785834786e785948424a5555314832372b2f6d67595169442b734a713647495768593651706c33346c366269527a706533793655424b452f4c6577752b54766e7138655a6d4b5a416a79357a4d6a562b495a325a63637039752f6e34414b62677133542f64664870554662363865586f537174526a3157585672304a554a434d7955484f3371395855584b71414b443038725a61415051554f554666674e30346b42617871614f46486d3272736b614d6942655953564b644d5665582b34656239453677364258633137724558465875376c494b466c4c487352385341565254446955732f54564f2f7142795541324d503133534d3230635142722f6533393138584973396658783457454a77726a37646656436c49414d6d2f333545574a6f48706363596f35544370383238644747756a6349742f75456a35782f76707841504b78302b5631504f7265586a337a667751706f7570616b687157456d56665148614e55745a45706b797168334d523566654d7932575435763071756c532b7239394f6e75426f4d4e325852353933674c2b6c773933433978543462705a4658783643564759627666475278475261684d364b514954554f465a7568426d524a477834474b4b6f5552717252386646677041516362547a393875734e34554131754a6d57614531414c4a64544f72474c6d66422b694a594e3065597955714268476f306f6556446c6a4261416c316e513152564c6a397838665079355269397930544366326852474954414253546f73713043635439377438315a68545670677730417a6e5256484d4936665256557a735757796f7a764c6d41373551514c59555a654d414e674555496f4e654743506a4b6a41744c756d754b6166485357367a47576353414942546778454a7754586b306855305855337373614d75507a6f636542706b57306d75365251455147515a4a5a756c5253783242794670756645386d666d39524f3434727237656a6d4c6f7742636c6f374973316b46513667665971382f424f4a6c6a38506550364a4f4b75386852697a434b73627859424d337975684e557151632b586c5330364b4e564d70756b666855366131413342654667475a764b6e6b647444424130494d696432677755646676707653475a3551785641426133535a3370584b774d6476426e464e2b4467744f616c54324151686a302f334673416c63795849616d6c646941345559467247485861466770385364317348726931687832497850687a346e516f384b64796f4d6c7945597a6146453250317a4a4351386a73676544714a2f46384a336d6d623774567430596c4a64487770526d4465487470417a5870537864514671436e5668426e446236625a7948356e355a5a566e655536334738364b2b386968722f47514738685549703570516b6f574e2f536f3070616952696b5179557970304d733461492b6978543344487370444c5330736f347a6a475645425a654241526d77484c627979775142724a636c7330576d656754726541674335637a6c4f663179674c545350554a444968452f71464c55304b384952326367706d374768394448566d56736f32556d39513037576e676a337a79704855424879504641587a4f5a71496f5249555351304b4559516757516f6875423936536b624a41566f3158644f4569712f784b46555059567255474754394151717264456b76684d6f4c6e734c6e584e6a4f4c46435473435566597643414b416f75394c4a6142616468784b766e433064764a716d7863417268414c5278426b595758457053384c7859435169424f67495330344b674c6d6b6148366d415935496e69643539742b594b614f6b3852426f4e476d334b746c66456933672b526a6f68794b62365975794471424e427a55614a5157446e432f306730726f5a637a7333793636755a6449347449416c525741347775666f4c425231764b71363155526f452b5253515a57576a7a7970566a614d6b6550584b444f344b6f776f366567625a524f71417852505a737a50664d51726d7a4a61444c55574535446172674e3834503745456d6f67496c526579516e6f536c62534b617a4b4f49497a5546586e744b454b4975646774424c4e704b7933696145497a6b396e4651686b2b5a6a52462f435938316678444e77536a77377241644a6f4261456f484b3851626d68594a684237737a3048765a546c4652786169472b7046534835556573317a635777304d6442377744754c487749725654486f45344b344473354c7a4a584646475a54443343434a59676754716e4e483431772f4b7342713752493538654f65594144356432444869574c33684863456770504e2b7462524763784450564f614842712f717357424942305537314e44326f6a653138437a626b354f766f4545514e365263316d3077457363774e55436b4162707054654634644748754c38594b526770656f564f36422f6a6331683246425357426961644275657472435130677952796e4e4259526c376b4a55554c6f7450335655425756374b755a7a34413861524d4b546e5a6d2f586439504a396977376c336e625253315a4774713230484f49714d3532467649526f51754e6b3463436b3244545a554f6e7544305764794d4356697838656c4e496f4d3932757969456c63456b726e6d377162794578555a556b2b77325359576c4e746a537967327838436d324153615752533132475735325542696232714f6c684249595153344558705355596e6c2f72794b36367277476b716c644f343258474241766f72567459534f344e3563654b496f594c4d696c367661677856537652454c2f73563270625941642b6f4476302f6e5a73556735775563366d48466764494a3868415269574d3246304b61516f556237634555396851466872736f2f43696569486f6e72584d303455374851496178514e7a774b476c68676d4e414a7471716a417450424f71454d5978556a6d6e57744c3159646c755559516d6c43474e6a483072773962364a434b2b7432786f5162736865776e5a55744b6141587550626a72546d584463555a346e41593432553873534459544e33742f756b71442f58496c74646c6339466d712f334c644c594267386a57325433744b35434b4a69686d55465531716d6f4d325235374a6c626c30326174586b6c6c4e5559414136656d36556b4d362f6e4f5661487773316d6564346b764265485954316c45435237784771345765566a535261466a754d45436251677159786d70795a6b4b747155675836727a6e564c767832316f7444304638464c796a596639555a6174524f7a687557416a39554146416175726b50524e747659533958424138453865627972354257706d70464f30424c6d69663439366b4f37624f6d47367934495a4f716362354b6c6f613831596b4174587252337937757370455232687947673673694f33797a6f77514943527751353549597143397a3167716f494f5967513072706a656e454d4d7964647644537a6a755a4a7330724a2b6b5167693978414356535969626d656f70666f664939746d62715153725848756f5630504339543175564d4d516e4f476e4a64786c41494d4b5152747569627265427070447435612f4365746b2f534f314f497542512b554279706c51713276397669754157372f747763457072513337773869756441475541365732356e79694f41614d645a366b4d5431774e6b632f4b43343246777a43497847344f564f3468302f446f6768634e313572696d446f514441524c45425a366e6f486e56676668744e33644771705475696167663878717347576764384937694651684d427a6a47354b2f39504d4342712b7439382f6366384646435173756675423958387a3054436f55647538576b6e754346677a72346a7173575657516a7236745463757a32792f2b357133456a6878716b5342685a6b306254614e3476346b39656a484e303745714a684950765774702f32763676726c4e62737a4754637367776534332f334c72484d562b2b2b71565837582f612f3262375152736648373435632f534d4f6a382f747a70394f727a5a6352322f684d645737776b386e6a514c4e6c39664c4b75494b514a5076317750627072686e702b6230764230654e6d5a64546b38506233657a7462666e5a362b32394b47496e71666d306a344446387644346877636746543849767a35757a30644c6462342f6b4d73553750686c3836347939507a3971414d4f6c646936663432646e5a6c57416e3638335a326274447565766b4c2b6941762f71625578693841377947523967687648767542355432416457764d45316f2f424b6d667a356754365542564b527a4242687541666b376741494a34632f587a6a365041307268734f6c57727358307778494b6c594a4570322b2b624731344d6b4c5a54416c322f4f616b50523441523063415964377050336945523145504d6e38624d4f784d504c7343306237412b70304566446b364b71454651434d41636c476a42354e514737436c6767336735664273314e454d7642683241643932317232472f6e4f7830654868484e5143484c595338516251484936473139327747413362586a6f616451477848397a736e2b466f644c67304f666c724e477741523375414977513865547361646c537a447a6a73416972577851676d5771505273443663764c65417346426270594f52414c7a385042792b365155456c513648585543706867484f35546363645a4339476b4246414c596c58414e2b67666544397333554869444d3367504541654831743948686f4e67434b732b48415a2f4434556872543330464550783041455a4d766f314750655551417236786f756774614c316c5a584d4e57434c416c355a7958675530726f624469355042384f74687641625130595a6f3552616c624143726b7776776d7462345142747062634442384f31652b523343694f543532376c307544574173487a582f5465416f54556544713561485945323048592f34756a6a7757425051736b5a444c546e685051567443642f44515a6a52394d47304671324d7545524152336c737a6277576c346a414c63624f41536f587730474638355833704e685432444b57502f7935545073712b3443347630386c36344249646831584c3471496337346c6a67393578774641532f575579396167466f4443446d66447251575356322b4b7145596374316236514b67686b426d48364144674e4865616c304a3932585259613766653237635347694f4e61304e364347674969574a4b512b3063696645433556714c77417438496a443139685349356c32434642754a4e5249634b567072534e6b6f476d76417372616f502f5132512b6f43554161786c547a646e6544416446494731445458746751414c556a67424e74432b6931415455425346695a554533626364424c77497344456d72392f77477141376a7a4c484d394b556b632f434670393139453967466c4d745a6a614e4675376d7541736a5a6541354979324f775741655653614e4c305a46707570516a7765726b4c364e68346f3746547245586b3434414541546e2b6173593278333246343732744c616a4838676c4a6577454a47627455396d537641796a334137707251476e426d6d7668426c434b6d4f77316c305a4741524a733577633273376658765a492b5933622b516b4a343767384c39784e4d77533971525a6d3975394177596e6755706c4f634d41793363617a44512b74734155384a666f54687a6f5947767579394e75415a39445a6654566971355848773246774a39303374613072727336667a5545632f7a7038542b702f32702f312f742f38412b724a44466c344139755541414141415355564f524b35435949495c783364273b76617220693d5b2764696d675f3137275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220733d27646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414841414141427743414d414141447850675235414141416231424d5645575a4141442f2f2f2b574141435a45684c7a354f535341414474323976713164585772713743686f61765656586e304e432f666e372b2b2f763538764b705330754e414143694f546e66774d446275626d5a4367717153456a323675713965586e52704b53474141446b794d6969516b4b36635847684e44544a6c4a537457566d644b69716149534764485232775947433161576b305454574c4141414376306c455156526f6765325759584f6a4942434764654e714c4e56456a4d6247746b6d622f502f665747424230574454752f4e6d626d37322f59514c374f4d43757842464c42614c78574b7857437757693856697356697366307962425230696d4a73414944544a39335a59374848434e416d7150554c547a6d785a2f626f787944642f5576734f6f7a63784759386859426d486c654d755945324f326b6c5654477744454d36547765304b77446a756c5074714f756e7369466a2f45664170334b465763427068584476484d4c5758693843796c764a6b42376c6d6a7057553076374f53526c623636626f34564c4c4f7445687044546e772f4a793353316f5746624c4c53774278517675477a744f4e63316135684168496e6b734b745838644f76566f65725a622f562f584d6c794a4d2b5971625a384a64767a486b4d42456a446252324158554a30736175626143566a677a767549552f32424769695177685947434f5a487a3065374a6148776667324975563331616744754f7a4a56777a2b30384c7761454b357545776367374f6959644b5a667539726962774450433043374f3855594964684e31716b495a6933376878466d716954495759537471545233533270505462735a675743585761556943724f2f6a34415267436d524870427331476d4250514b364c5978504f414b6a4379574f536b57546e506c6a6f4f5036774e46716765393533746b7a47736533385a53717543516470452f5535366634694659425469534e4a77654547316d504a6b4d6b7267394d71617734594252524b6c494e754d4c6177474a3767436b514b52564e5257763159566746574769565a646f3177385867674e434d4662746243316a6365715871416a6834475a63557831586f56774d3264374e486f4574464e64643872674c6366514e307165694b30393848326c5455536668546f436f6a376a355556364f37783754563359657a797733785252394f6f63786a4b756f455656507339625230487872506653704552754e4b49635357526a6171535365777949536f76542b2b6e49526f615843717468636f46585553536945536370514963566f737037506e556f6f42612b4c4e39743830543242547354334d58306a46556f687a59425943706a3751383675416b626b5664524a4f6763464831426f52556970576438446c434a736b3955585076706c56657341334d5a695478747939535772324749552f4a666a794a754a4d49665053684f453734476d4a78324b78574377576938566973566773466f7646597630332b67492b6453615a504562466d6741414141424a52553545726b4a6767675c7833645c783364273b76617220693d5b2764696d675f3139275d3b5f736574496d6167657353726328692c73293b7d2928293b2866756e6374696f6e28297b76617220653d27704d524f586250624472505339414f426d4b50594251273b2866756e6374696f6e28297b76617220613d652c623d77696e646f772e706572666f726d616e6365262677696e646f772e706572666f726d616e63652e6e617669676174696f6e3b622626323d3d622e74797065262677696e646f772e70696e6728222f67656e5f3230343f63743d6261636b627574746f6e2665693d222b61293b7d292e63616c6c2874686973293b7d2928293b2866756e6374696f6e28297b2866756e6374696f6e28297b676f6f676c652e637363743d7b7d3b676f6f676c652e637363742e70733d27414f765661773144704144365477704f5055674e344e6f72467065555c7832367573745c78336431353635353239363336323834373838273b7d2928293b7d2928293b2866756e6374696f6e28297b2866756e6374696f6e28297b676f6f676c652e637363742e72643d747275653b7d2928293b7d2928293b2866756e6374696f6e28297b77696e646f772e78703d66756e6374696f6e2861297b66756e6374696f6e206528682c662c67297b72657475726e227870222b282278223d3d663f2263223a227822292b677d666f722876617220623d2f5c62787028787c6329285c643f295c622f3b613b297b76617220633d612e636c6173734e616d652c643d632e6d617463682862293b69662864297b612e636c6173734e616d653d632e7265706c61636528622c65293b6966282263223d3d645b315d29666f7228613d612e676574456c656d656e747342795461674e616d652822696d6722292c623d303b623c612e6c656e6774683b2b2b6229696628633d615b625d2c643d632e6765744174747269627574652822646174612d6c6c222929632e7372633d642c632e72656d6f76654174747269627574652822646174612d6c6c22293b627265616b7d613d612e706172656e74456c656d656e747d7d3b7d2928293b676f6f676c652e647274792626676f6f676c652e6472747928293b3c2f7363726970743e3c2f626f64793e3c2f68746d6c3e", + "rawHeaders": [ + "Content-Type", + "text/html; charset=ISO-8859-1", + "Date", + "Sat, 10 Aug 2019 01:21:31 GMT", + "Expires", + "-1", + "Cache-Control", + "private, max-age=0", + "P3P", + "CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\"", + "Server", + "gws", + "X-XSS-Protection", + "0", + "X-Frame-Options", + "SAMEORIGIN", + "Set-Cookie", + "1P_JAR=2019-08-10-01; expires=Mon, 09-Sep-2019 01:21:31 GMT; path=/; domain=.google.com", + "Set-Cookie", + "CGIC=IiFhcHBsaWNhdGlvbi9qc29uLCB0ZXh0L3BsYWluLCAqLyo; expires=Thu, 06-Feb-2020 01:21:31 GMT; path=/complete/search; domain=.google.com; HttpOnly", + "Set-Cookie", + "CGIC=IiFhcHBsaWNhdGlvbi9qc29uLCB0ZXh0L3BsYWluLCAqLyo; expires=Thu, 06-Feb-2020 01:21:31 GMT; path=/search; domain=.google.com; HttpOnly", + "Set-Cookie", + "NID=188=vTMutucOBO-Yl5bpVtVnzkN1voOukQ24RkD0wuuzeNL_BDPMEB90MqBF06HFaILh_fs-PO8JGLhIjkSb3nxl9Rzf8L7CxJtk_yJF0aEgi2znY0rMT_dQr6_5tYfVNKU9u0d2BoXOVOWHEN3ZzaD7q6yRUb44yH3vjL0kue6Ki0s; expires=Sun, 09-Feb-2020 01:21:31 GMT; path=/; domain=.google.com; HttpOnly", + "Accept-Ranges", + "none", + "Vary", + "Accept-Encoding", + "Connection", + "close" + ], + "responseIsBinary": true + } +] diff --git a/packages/opentelemetry-plugin-http/test/functionals/http-disable.test.ts b/packages/opentelemetry-plugin-http/test/functionals/http-disable.test.ts new file mode 100644 index 00000000000..d4c5e52a91d --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/functionals/http-disable.test.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import * as http from 'http'; +import * as nock from 'nock'; +import * as sinon from 'sinon'; + +import { plugin } from '../../src/http'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { NodeTracer } from '@opentelemetry/node-tracer'; +import { NoopLogger } from '@opentelemetry/core'; +import { AddressInfo } from 'net'; +import { DummyPropagation } from '../utils/DummyPropagation'; +import { httpRequest } from '../utils/httpRequest'; + +describe('HttpPlugin', () => { + let server: http.Server; + let serverPort = 0; + + describe('disable()', () => { + const scopeManager = new AsyncHooksScopeManager(); + const httpTextFormat = new DummyPropagation(); + const logger = new NoopLogger(); + const tracer = new NodeTracer({ + scopeManager, + logger, + httpTextFormat, + }); + before(() => { + nock.cleanAll(); + nock.enableNetConnect(); + + plugin.enable(http, tracer, tracer.logger); + server = http.createServer((request, response) => { + response.end('Test Server Response'); + }); + + server.listen(serverPort); + server.once('listening', () => { + serverPort = (server.address() as AddressInfo).port; + }); + }); + + beforeEach(() => { + tracer.startSpan = sinon.spy(); + tracer.withSpan = sinon.spy(); + tracer.recordSpanData = sinon.spy(); + }); + + afterEach(() => { + sinon.restore(); + }); + + after(() => { + server.close(); + }); + describe('unpatch()', () => { + it('should not call tracer methods for creating span', async () => { + plugin.disable(); + const testPath = '/incoming/unpatch/'; + + const options = { host: 'localhost', path: testPath, port: serverPort }; + + await httpRequest.get(options).then(result => { + assert.strictEqual( + (tracer.startSpan as sinon.SinonSpy).called, + false + ); + assert.strictEqual((tracer.withSpan as sinon.SinonSpy).called, false); + assert.strictEqual( + (tracer.recordSpanData as sinon.SinonSpy).called, + false + ); + }); + }); + }); + }); +}); diff --git a/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts b/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts new file mode 100644 index 00000000000..75d14b3a196 --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/functionals/http-enable.test.ts @@ -0,0 +1,282 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NoopLogger } from '@opentelemetry/core'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { SpanKind, Span } from '@opentelemetry/types'; +import * as assert from 'assert'; +import * as http from 'http'; +import * as nock from 'nock'; +import { HttpPlugin, plugin } from '../../src/http'; +import { assertSpan } from '../utils/assertSpan'; +import { DummyPropagation } from '../utils/DummyPropagation'; +import { httpRequest } from '../utils/httpRequest'; +import { TracerTest } from '../utils/TracerTest'; +import { SpanAuditProcessor } from '../utils/SpanAuditProcessor'; + +let server: http.Server; +const serverPort = 12345; +const protocol = 'http'; +const hostname = 'localhost'; +const pathname = '/test'; +const audit = new SpanAuditProcessor(); + +function doNock( + hostname: string, + path: string, + httpCode: number, + respBody: string, + times?: number +) { + const i = times || 1; + nock(`${protocol}://${hostname}`) + .get(path) + .times(i) + .reply(httpCode, respBody); +} + +export const customAttributeFunction = (span: Span): void => { + span.setAttribute('span kind', SpanKind.CLIENT); +}; + +describe('HttpPlugin', () => { + it('should return a plugin', () => { + assert.ok(plugin instanceof HttpPlugin); + }); + + it('should match version', () => { + assert.strictEqual(process.versions.node, plugin.version); + }); + + it('moduleName should be http', () => { + assert.strictEqual('http', plugin.moduleName); + }); + + describe('enable()', () => { + const scopeManager = new AsyncHooksScopeManager(); + const httpTextFormat = new DummyPropagation(); + const logger = new NoopLogger(); + const tracer = new TracerTest( + { + scopeManager, + logger, + httpTextFormat, + }, + audit + ); + beforeEach(() => { + audit.reset(); + }); + + before(() => { + plugin.enable(http, tracer, tracer.logger); + const ignoreConfig = [ + `http://${hostname}:${serverPort}/ignored/string`, + /\/ignored\/regexp$/i, + (url: string) => url.endsWith(`/ignored/function`), + ]; + plugin.options = { + ignoreIncomingPaths: ignoreConfig, + ignoreOutgoingUrls: ignoreConfig, + applyCustomAttributesOnSpan: customAttributeFunction, + }; + + server = http.createServer((request, response) => { + response.end('Test Server Response'); + }); + + server.listen(serverPort); + }); + + after(() => { + server.close(); + plugin.disable(); + }); + + it('should generate valid spans (client side and server side)', done => { + httpRequest + .get(`http://${hostname}:${serverPort}${pathname}`) + .then(result => { + const spans = audit.processSpans(); + const outgoingSpan = spans[0]; + const incomingSpan = spans[1]; + + const validations = { + hostname, + httpStatusCode: result.statusCode!, + httpMethod: result.method!, + pathname, + resHeaders: result.resHeaders, + reqHeaders: result.reqHeaders, + }; + + assert.strictEqual(spans.length, 2); + assertSpan(incomingSpan, SpanKind.SERVER, validations); + assertSpan(outgoingSpan, SpanKind.CLIENT, validations); + done(); + }); + }); + + const httpErrorCodes = [400, 401, 403, 404, 429, 501, 503, 504, 500]; + + for (let i = 0; i < httpErrorCodes.length; i++) { + it(`should test span for GET requests with http error ${httpErrorCodes[i]}`, async () => { + const testPath = '/outgoing/rootSpan/1'; + + doNock( + hostname, + testPath, + httpErrorCodes[i], + httpErrorCodes[i].toString() + ); + + const isReset = audit.processSpans().length === 0; + assert.ok(isReset); + await httpRequest + .get(`${protocol}://${hostname}${testPath}`) + .then(result => { + const spans = audit.processSpans(); + assert.strictEqual(result.data, httpErrorCodes[i].toString()); + assert.strictEqual(spans.length, 1); + + const validations = { + hostname, + httpStatusCode: result.statusCode!, + httpMethod: 'GET', + pathname: testPath, + resHeaders: result.resHeaders, + reqHeaders: result.reqHeaders, + }; + + assertSpan(spans[0], SpanKind.CLIENT, validations); + }); + }); + } + + it('should create a child span for GET requests', done => { + const testPath = '/outgoing/rootSpan/childs/1'; + doNock(hostname, testPath, 200, 'Ok'); + const name = 'TestRootSpan'; + const span = tracer.startSpan(name); + tracer.withSpan(span, () => { + httpRequest.get(`${protocol}://${hostname}${testPath}`).then(result => { + const spans = audit.processSpans(); + assert.ok(spans[0].name.indexOf('TestRootSpan') >= 0); + assert.strictEqual(spans.length, 2); + assert.ok(spans[1].name.indexOf(testPath) >= 0); + assert.strictEqual( + spans[1].spanContext.traceId, + spans[0].spanContext.traceId + ); + const validations = { + hostname, + httpStatusCode: result.statusCode!, + httpMethod: 'GET', + pathname: testPath, + resHeaders: result.resHeaders, + reqHeaders: result.reqHeaders, + }; + assertSpan(spans[1], SpanKind.CLIENT, validations); + assert.notStrictEqual( + spans[1].spanContext.spanId, + spans[0].spanContext.spanId + ); + done(); + }); + }); + }); + + for (let i = 0; i < httpErrorCodes.length; i++) { + it(`should test a child spans for GET requests with http error ${httpErrorCodes[i]}`, done => { + const testPath = '/outgoing/rootSpan/childs/1'; + doNock( + hostname, + testPath, + httpErrorCodes[i], + httpErrorCodes[i].toString() + ); + const name = 'TestRootSpan'; + const span = tracer.startSpan(name); + tracer.withSpan(span, () => { + httpRequest + .get(`${protocol}://${hostname}${testPath}`) + .then(result => { + const spans = audit.processSpans(); + assert.ok(spans[0].name.indexOf('TestRootSpan') >= 0); + assert.strictEqual(spans.length, 2); + assert.ok(spans[1].name.indexOf(testPath) >= 0); + assert.strictEqual( + spans[1].spanContext.traceId, + spans[0].spanContext.traceId + ); + const validations = { + hostname, + httpStatusCode: result.statusCode!, + httpMethod: 'GET', + pathname: testPath, + resHeaders: result.resHeaders, + reqHeaders: result.reqHeaders, + }; + assertSpan(spans[1], SpanKind.CLIENT, validations); + assert.notStrictEqual( + spans[1].spanContext.spanId, + spans[0].spanContext.spanId + ); + done(); + }); + }); + }); + } + // TODO: uncomment once https://github.com/open-telemetry/opentelemetry-js/pull/146 is merged + // it('should create multiple child spans for GET requests', (done) => { + // const testPath = '/outgoing/rootSpan/childs'; + // const num = 5; + // doNock(hostname, testPath, 200, 'Ok', num); + // const name = 'TestRootSpan'; + // const span = tracer.startSpan(name); + // const auditedSpan = audit.processSpans()[0]; + // assert.ok(auditedSpan.name.indexOf('TestRootSpan') >= 0); + // tracer.withSpan(span, async () => { + // for (let i = 0; i < num; i++) { + // await httpRequest.get(`${ protocol }://${ hostname }${ testPath }`).then(result => { + // const spans = audit.processSpans(); + // const startChildIndex = i + 1; + // assert.strictEqual(spans.length, startChildIndex + 1); + // assert.ok(spans[startChildIndex].name.indexOf(testPath) >= 0); + // assert.strictEqual(auditedSpan.spanContext.traceId, spans[startChildIndex].spanContext.traceId); + // }); + // } + // const spans = audit.processSpans(); + // // 5 child spans ended + 1 span (root) + // assert.strictEqual(spans.length, 6); + // span.end(); + // done(); + // }); + // }); + + for (const ignored of ['string', 'function', 'regexp']) { + it(`should not trace ignored requests with type ${ignored}`, async () => { + const testPath = `/ignored/${ignored}`; + doNock(hostname, testPath, 200, 'Ok'); + + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 0); + await httpRequest.get(`${protocol}://${hostname}${testPath}`); + assert.strictEqual(spans.length, 0); + }); + } + }); +}); diff --git a/packages/opentelemetry-plugin-http/test/functionals/http-package.test.ts b/packages/opentelemetry-plugin-http/test/functionals/http-package.test.ts new file mode 100644 index 00000000000..6fc8a726f6f --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/functionals/http-package.test.ts @@ -0,0 +1,138 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NoopLogger } from '@opentelemetry/core'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { SpanKind, Span } from '@opentelemetry/types'; +import * as assert from 'assert'; +import * as http from 'http'; +import * as nock from 'nock'; +import { plugin } from '../../src/http'; +import { assertSpan } from '../utils/assertSpan'; +import { DummyPropagation } from '../utils/DummyPropagation'; +import { TracerTest } from '../utils/TracerTest'; +import { SpanAuditProcessor } from '../utils/SpanAuditProcessor'; +import * as url from 'url'; +import axios, { AxiosResponse } from 'axios'; +import * as superagent from 'superagent'; +import * as got from 'got'; +import * as request from 'request-promise-native'; +import * as path from 'path'; + +const audit = new SpanAuditProcessor(); + +export const customAttributeFunction = (span: Span): void => { + span.setAttribute('span kind', SpanKind.CLIENT); +}; + +describe('Packages', () => { + describe('get', () => { + const scopeManager = new AsyncHooksScopeManager(); + const httpTextFormat = new DummyPropagation(); + const logger = new NoopLogger(); + + const tracer = new TracerTest( + { + scopeManager, + logger, + httpTextFormat, + }, + audit + ); + beforeEach(() => { + audit.reset(); + }); + + before(() => { + plugin.enable(http, tracer, tracer.logger); + }); + + after(() => { + // back to normal + nock.cleanAll(); + nock.enableNetConnect(); + }); + + let resHeaders: http.IncomingHttpHeaders; + [ + { name: 'axios', httpPackage: axios }, //keep first + { name: 'superagent', httpPackage: superagent }, + { name: 'got', httpPackage: { get: (url: string) => got(url) } }, + { + name: 'request', + httpPackage: { get: (url: string) => request(url) }, + }, + ].forEach(({ name, httpPackage }) => { + it(`should create a span for GET requests and add propagation headers by using ${name} package`, async () => { + if (process.versions.node.startsWith('12') && name === 'got') { + // got complains with nock and node version 12+ + // > RequestError: The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type function + // so let's make a real call + nock.cleanAll(); + nock.enableNetConnect(); + } else { + nock.load(path.join(__dirname, '../', '/fixtures/google.json')); + } + + const urlparsed = url.parse( + name === 'got' && process.versions.node.startsWith('12') + ? // there is an issue with got 9.6 version and node 12 when redirecting so url above will not work + // https://github.com/nock/nock/pull/1551 + // https://github.com/sindresorhus/got/commit/bf1aa5492ae2bc78cbbec6b7d764906fb156e6c2#diff-707a4781d57c42085155dcb27edb9ccbR258 + // TODO: check if this is still the case when new version + 'http://info.cern.ch/' + : `http://www.google.com/search?q=axios&oq=axios&aqs=chrome.0.69i59l2j0l3j69i60.811j0j7&sourceid=chrome&ie=UTF-8` + ); + const result = await httpPackage.get(urlparsed.href!); + if (!resHeaders) { + const res = result as AxiosResponse<{}>; + resHeaders = res.headers; + } + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 1); + assert.ok(spans[0].name.indexOf(`GET ${urlparsed.pathname}`) >= 0); + + const span = spans[0]; + const validations = { + hostname: urlparsed.hostname!, + httpStatusCode: 200, + httpMethod: 'GET', + pathname: urlparsed.pathname!, + path: urlparsed.path, + resHeaders, + }; + + switch (name) { + case 'axios': + assert.ok( + result.request._headers[DummyPropagation.TRACE_CONTEXT_KEY] + ); + assert.ok( + result.request._headers[DummyPropagation.SPAN_CONTEXT_KEY] + ); + break; + case 'got': + case 'superagent': + break; + default: + break; + } + assert.strictEqual(span.attributes['span kind'], SpanKind.CLIENT); + assertSpan(span, SpanKind.CLIENT, validations); + }); + }); + }); +}); diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts new file mode 100644 index 00000000000..232f1ec5558 --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -0,0 +1,202 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import * as url from 'url'; +import { CanonicalCode, Attributes } from '@opentelemetry/types'; +import { IgnoreMatcher } from '../../src/types'; +import { Utils } from '../../src/utils'; +import * as http from 'http'; + +describe('Utils', () => { + describe('parseResponseStatus()', () => { + it('should return UNKNOWN code by default', () => { + const status = Utils.parseResponseStatus( + (undefined as unknown) as number + ); + assert.deepStrictEqual(status, { code: CanonicalCode.UNKNOWN }); + }); + + it('should return OK for Success HTTP status code', () => { + for (let index = 200; index < 400; index++) { + const status = Utils.parseResponseStatus(index); + assert.deepStrictEqual(status, { code: CanonicalCode.OK }); + } + }); + + it('should not return OK for Bad HTTP status code', () => { + for (let index = 400; index <= 504; index++) { + const status = Utils.parseResponseStatus(index); + assert.notStrictEqual(status.code, CanonicalCode.OK); + } + }); + }); + describe('hasExpectHeader()', () => { + it('should throw if no option', () => { + try { + Utils.hasExpectHeader('' as http.RequestOptions); + assert.fail(); + } catch (ignore) {} + }); + + it('should not throw if no headers', () => { + const result = Utils.hasExpectHeader({} as http.RequestOptions); + assert.strictEqual(result, false); + }); + + it('should return true on Expect (no case sensitive)', () => { + for (const headers of [{ Expect: 1 }, { expect: 1 }, { ExPect: 1 }]) { + const result = Utils.hasExpectHeader({ + headers, + } as http.RequestOptions); + assert.strictEqual(result, true); + } + }); + }); + + describe('getRequestInfo()', () => { + it('should get options object', () => { + const result = Utils.getRequestInfo('http://google.fr/'); + assert.strictEqual(result.optionsParsed.hostname, 'google.fr'); + assert.strictEqual(result.optionsParsed.protocol, 'http:'); + assert.strictEqual(result.optionsParsed.path, '/'); + assert.strictEqual(result.pathname, '/'); + }); + }); + + describe('satisfiesPattern()', () => { + it('string pattern', () => { + const answer1 = Utils.satisfiesPattern('/test/1', '/test/1'); + assert.strictEqual(answer1, true); + const answer2 = Utils.satisfiesPattern('/test/1', '/test/11'); + assert.strictEqual(answer2, false); + }); + + it('regex pattern', () => { + const answer1 = Utils.satisfiesPattern('/TeSt/1', /\/test/i); + assert.strictEqual(answer1, true); + const answer2 = Utils.satisfiesPattern('/2/tEst/1', /\/test/); + assert.strictEqual(answer2, false); + }); + + it('should throw if type is unknown', () => { + try { + Utils.satisfiesPattern('/TeSt/1', (true as unknown) as IgnoreMatcher); + assert.fail(); + } catch (error) { + assert.strictEqual(error instanceof TypeError, true); + } + }); + + it('function pattern', () => { + const answer1 = Utils.satisfiesPattern( + '/test/home', + (url: string) => url === '/test/home' + ); + assert.strictEqual(answer1, true); + const answer2 = Utils.satisfiesPattern( + '/test/home', + (url: string) => url !== '/test/home' + ); + assert.strictEqual(answer2, false); + }); + }); + + describe('isIgnored()', () => { + beforeEach(() => { + Utils.satisfiesPattern = sinon.spy(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should call isSatisfyPattern, n match', () => { + const answer1 = Utils.isIgnored('/test/1', ['/test/11']); + assert.strictEqual(answer1, false); + assert.strictEqual( + (Utils.satisfiesPattern as sinon.SinonSpy).callCount, + 1 + ); + }); + + it('should call isSatisfyPattern, match', () => { + const answer1 = Utils.isIgnored('/test/1', ['/test/11']); + assert.strictEqual(answer1, false); + assert.strictEqual( + (Utils.satisfiesPattern as sinon.SinonSpy).callCount, + 1 + ); + }); + + it('should not call isSatisfyPattern', () => { + Utils.isIgnored('/test/1', []); + assert.strictEqual( + (Utils.satisfiesPattern as sinon.SinonSpy).callCount, + 0 + ); + }); + + it('should return false on empty list', () => { + const answer1 = Utils.isIgnored('/test/1', []); + assert.strictEqual(answer1, false); + }); + + it('should not throw and return false when list is undefined', () => { + const answer2 = Utils.isIgnored('/test/1', undefined); + assert.strictEqual(answer2, false); + }); + }); + + describe('getUrlFromIncomingRequest()', () => { + it('should return absolute url with localhost', () => { + const path = '/test/1'; + const result = Utils.getUrlFromIncomingRequest(url.parse(path), {}); + assert.strictEqual(result, `http://localhost${path}`); + }); + it('should return absolute url', () => { + const absUrl = 'http://www.google/test/1?query=1'; + const result = Utils.getUrlFromIncomingRequest(url.parse(absUrl), {}); + assert.strictEqual(result, absUrl); + }); + it('should return default url', () => { + const result = Utils.getUrlFromIncomingRequest(null, {}); + assert.strictEqual(result, 'http://localhost/'); + }); + }); + describe('setSpanOnError()', () => { + it('should call span methods when we get an error event', done => { + /* tslint:disable-next-line:no-any */ + const span: any = { + setAttributes: (obj: Attributes) => {}, + setStatus: (status: unknown) => {}, + end: () => {}, + }; + sinon.spy(span, 'setAttributes'); + sinon.spy(span, 'setStatus'); + sinon.spy(span, 'end'); + const req = http.get('http://noop'); + Utils.setSpanOnError(span, req); + req.on('error', () => { + assert.strictEqual(span.setAttributes.callCount, 1); + assert.strictEqual(span.setStatus.callCount, 1); + assert.strictEqual(span.end.callCount, 1); + done(); + }); + }); + }); +}); diff --git a/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts b/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts new file mode 100644 index 00000000000..5a5f3ec1179 --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts @@ -0,0 +1,210 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NoopLogger } from '@opentelemetry/core'; +import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks'; +import { SpanKind, Span } from '@opentelemetry/types'; +import * as assert from 'assert'; +import * as http from 'http'; +import { plugin } from '../../src/http'; +import { assertSpan } from '../utils/assertSpan'; +import { DummyPropagation } from '../utils/DummyPropagation'; +import { httpRequest } from '../utils/httpRequest'; +import { TracerTest } from '../utils/TracerTest'; +import { SpanAuditProcessor } from '../utils/SpanAuditProcessor'; +import * as url from 'url'; +import { Utils } from '../utils/Utils'; + +const serverPort = 12345; +const hostname = 'localhost'; +const audit = new SpanAuditProcessor(); + +export const customAttributeFunction = (span: Span): void => { + span.setAttribute('span kind', SpanKind.CLIENT); +}; + +describe('HttpPlugin Integration tests', () => { + describe('enable()', () => { + before(function(done) { + // mandatory + if (process.env.CI) { + done(); + return; + } + + Utils.checkInternet(isConnected => { + if (!isConnected) { + this.skip(); + // don't disturbe people + } + done(); + }); + }); + + const scopeManager = new AsyncHooksScopeManager(); + const httpTextFormat = new DummyPropagation(); + const logger = new NoopLogger(); + const tracer = new TracerTest( + { + scopeManager, + logger, + httpTextFormat, + }, + audit + ); + beforeEach(() => { + audit.reset(); + }); + + before(() => { + plugin.disable(); + plugin.enable(http, tracer, tracer.logger); + const ignoreConfig = [ + `http://${hostname}:${serverPort}/ignored/string`, + /\/ignored\/regexp$/i, + (url: string) => url.endsWith(`/ignored/function`), + ]; + plugin.options = { + ignoreIncomingPaths: ignoreConfig, + ignoreOutgoingUrls: ignoreConfig, + applyCustomAttributesOnSpan: customAttributeFunction, + }; + }); + + after(() => { + plugin.disable(); + }); + + it('should create a rootSpan for GET requests and add propagation headers', async () => { + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 0); + await httpRequest.get(`http://google.fr/?query=test`).then(result => { + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 1); + assert.ok(spans[0].name.indexOf('GET /') >= 0); + + const span = spans[0]; + const validations = { + hostname: 'google.fr', + httpStatusCode: result.statusCode!, + httpMethod: 'GET', + pathname: '/', + path: '/?query=test', + resHeaders: result.resHeaders, + reqHeaders: result.reqHeaders, + }; + assertSpan(span, SpanKind.CLIENT, validations); + }); + }); + + it('custom attributes should show up on client spans', async () => { + await httpRequest.get(`http://google.fr/`).then(result => { + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 1); + assert.ok(spans[0].name.indexOf('GET /') >= 0); + + const span = spans[0]; + const validations = { + hostname: 'google.fr', + httpStatusCode: result.statusCode!, + httpMethod: 'GET', + pathname: '/', + resHeaders: result.resHeaders, + reqHeaders: result.reqHeaders, + }; + assert.strictEqual(span.attributes['span kind'], SpanKind.CLIENT); + assertSpan(span, SpanKind.CLIENT, validations); + }); + }); + + it('should create a span for GET requests and add propagation headers with Expect headers', async () => { + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 0); + const options = Object.assign( + { headers: { Expect: '100-continue' } }, + url.parse('http://google.fr/') + ); + await httpRequest.get(options).then(result => { + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 1); + assert.ok(spans[0].name.indexOf('GET /') >= 0); + + const span = spans[0]; + const validations = { + hostname: 'google.fr', + httpStatusCode: 301, + httpMethod: 'GET', + pathname: '/', + resHeaders: result.resHeaders, + reqHeaders: result.reqHeaders, + }; + try { + assertSpan(span, SpanKind.CLIENT, validations); + } catch (error) { + // temporary redirect is also correct + validations.httpStatusCode = 307; + assertSpan(span, SpanKind.CLIENT, validations); + } + }); + }); + for (const headers of [ + { Expect: '100-continue', 'user-agent': 'http-plugin-test' }, + { 'user-agent': 'http-plugin-test' }, + ]) { + it(`should create a span for GET requests and add propagation when using the following signature: http.get(url, options, callback) and following headers: ${JSON.stringify( + headers + )}`, done => { + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 0); + const options = { headers }; + http.get('http://google.fr/', options, (resp: http.IncomingMessage) => { + const res = (resp as unknown) as http.IncomingMessage & { + req: http.IncomingMessage; + }; + let data = ''; + resp.on('data', chunk => { + data += chunk; + }); + resp.on('end', () => { + const spans = audit.processSpans(); + assert.strictEqual(spans.length, 1); + assert.ok(spans[0].name.indexOf('GET /') >= 0); + const validations = { + hostname: 'google.fr', + httpStatusCode: 301, + httpMethod: 'GET', + pathname: '/', + resHeaders: resp.headers, + /* tslint:disable:no-any */ + reqHeaders: (res.req as any).getHeaders + ? (res.req as any).getHeaders() + : (res.req as any)._headers, + /* tslint:enable:no-any */ + }; + assert.ok(data); + assert.ok( + validations.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY] + ); + assert.ok( + validations.reqHeaders[DummyPropagation.SPAN_CONTEXT_KEY] + ); + done(); + }); + }); + }); + } + }); +}); diff --git a/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts b/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts new file mode 100644 index 00000000000..4d931e01e3e --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/utils/DummyPropagation.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { SpanContext, HttpTextFormat } from '@opentelemetry/types'; + +export class DummyPropagation implements HttpTextFormat { + static TRACE_CONTEXT_KEY = 'x-dummy-trace-id'; + static SPAN_CONTEXT_KEY = 'x-dummy-span-id'; + extract(format: string, carrier: unknown): SpanContext { + return { + traceId: 'ca35dd5daef1401de22bcee7b7069195', + spanId: DummyPropagation.SPAN_CONTEXT_KEY, + }; + } + inject( + spanContext: SpanContext, + format: string, + headers: { [custom: string]: string } + ): void { + headers[DummyPropagation.TRACE_CONTEXT_KEY] = spanContext.traceId; + headers[DummyPropagation.SPAN_CONTEXT_KEY] = spanContext.spanId; + } +} diff --git a/packages/opentelemetry-plugin-http/test/utils/SpanAuditProcessor.ts b/packages/opentelemetry-plugin-http/test/utils/SpanAuditProcessor.ts new file mode 100644 index 00000000000..d7f56f7bb4c --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/utils/SpanAuditProcessor.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Span, ReadableSpan } from '@opentelemetry/basic-tracer'; +export class SpanAuditProcessor { + private _spans: Span[]; + constructor() { + this._spans = []; + } + + push(span: Span) { + this._spans.push(span); + } + + processSpans(): ReadableSpan[] { + return this._spans.map(span => span.toReadableSpan()); + } + + reset() { + this._spans = []; + } +} diff --git a/packages/opentelemetry-plugin-http/test/utils/TracerTest.ts b/packages/opentelemetry-plugin-http/test/utils/TracerTest.ts new file mode 100644 index 00000000000..6bc4ef865af --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/utils/TracerTest.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SpanOptions } from '@opentelemetry/types'; +import { NodeTracer } from '@opentelemetry/node-tracer'; +import { BasicTracerConfig, Span } from '@opentelemetry/basic-tracer'; +import { SpanAuditProcessor } from './SpanAuditProcessor'; + +// TODO: remove these once we have exporter feature +// https://github.com/open-telemetry/opentelemetry-js/pull/149 +export class TracerTest extends NodeTracer { + constructor(config: BasicTracerConfig, public audit: SpanAuditProcessor) { + super(config); + } + + startSpan(name: string, options?: SpanOptions): Span { + const span = super.startSpan(name, options) as Span; + this.audit.push(span); + return span; + } +} diff --git a/packages/opentelemetry-plugin-http/test/utils/Utils.ts b/packages/opentelemetry-plugin-http/test/utils/Utils.ts new file mode 100644 index 00000000000..55448566049 --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/utils/Utils.ts @@ -0,0 +1,29 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as dns from 'dns'; + +export class Utils { + static checkInternet(cb: (isConnected: boolean) => void) { + dns.lookup('google.com', err => { + if (err && err.code === 'ENOTFOUND') { + cb(false); + } else { + cb(true); + } + }); + } +} diff --git a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts new file mode 100644 index 00000000000..f0d832f3f1a --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts @@ -0,0 +1,97 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SpanKind } from '@opentelemetry/types'; +import * as assert from 'assert'; +import * as http from 'http'; +import { AttributeNames } from '../../src/enums/AttributeNames'; +import { HttpPlugin } from '../../src/http'; +import { Utils } from '../../src/utils'; +import { DummyPropagation } from './DummyPropagation'; +import { ReadableSpan } from '@opentelemetry/basic-tracer'; + +export const assertSpan = ( + span: ReadableSpan, + kind: SpanKind, + validations: { + httpStatusCode: number; + httpMethod: string; + resHeaders: http.IncomingHttpHeaders; + hostname: string; + pathname: string; + reqHeaders?: http.OutgoingHttpHeaders; + path?: string; + } +) => { + assert.strictEqual(span.spanContext.traceId.length, 32); + assert.strictEqual(span.spanContext.spanId.length, 16); + assert.strictEqual(span.kind, kind); + assert.strictEqual( + span.name, + `${validations.httpMethod} ${validations.pathname}` + ); + assert.strictEqual( + span.attributes[AttributeNames.COMPONENT], + HttpPlugin.component + ); + assert.strictEqual( + span.attributes[AttributeNames.HTTP_ERROR_MESSAGE], + span.status.message + ); + assert.strictEqual( + span.attributes[AttributeNames.HTTP_HOSTNAME], + validations.hostname + ); + assert.strictEqual( + span.attributes[AttributeNames.HTTP_METHOD], + validations.httpMethod + ); + assert.strictEqual( + span.attributes[AttributeNames.HTTP_PATH], + validations.path || validations.pathname + ); + assert.strictEqual( + span.attributes[AttributeNames.HTTP_STATUS_CODE], + validations.httpStatusCode + ); + assert.ok(span.endTime); + assert.strictEqual(span.links.length, 0); + assert.strictEqual(span.events.length, 0); + assert.deepStrictEqual( + span.status, + Utils.parseResponseStatus(validations.httpStatusCode) + ); + + assert.ok(span.startTime < span.endTime); + assert.ok(span.endTime > 0); + + if (validations.reqHeaders) { + const userAgent = validations.reqHeaders['user-agent']; + if (userAgent) { + assert.strictEqual( + span.attributes[AttributeNames.HTTP_USER_AGENT], + userAgent + ); + } + } + + if (span.kind === SpanKind.SERVER) { + assert.strictEqual(span.parentSpanId, DummyPropagation.SPAN_CONTEXT_KEY); + } else if (validations.reqHeaders) { + assert.ok(validations.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY]); + assert.ok(validations.reqHeaders[DummyPropagation.SPAN_CONTEXT_KEY]); + } +}; diff --git a/packages/opentelemetry-plugin-http/test/utils/httpRequest.ts b/packages/opentelemetry-plugin-http/test/utils/httpRequest.ts new file mode 100644 index 00000000000..0efbdfb3a1b --- /dev/null +++ b/packages/opentelemetry-plugin-http/test/utils/httpRequest.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2019, OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as http from 'http'; +import * as url from 'url'; +import { RequestOptions } from 'https'; + +export const httpRequest = { + get: ( + options: string | RequestOptions + ): Promise<{ + data: string; + statusCode: number | undefined; + resHeaders: http.IncomingHttpHeaders; + reqHeaders: http.OutgoingHttpHeaders; + method: string | undefined; + }> => { + const _options = + typeof options === 'string' + ? Object.assign(url.parse(options), { + headers: { + 'user-agent': 'http-plugin-test', + }, + }) + : options; + return new Promise((resolve, reject) => { + return http.get(_options, (resp: http.IncomingMessage) => { + const res = (resp as unknown) as http.IncomingMessage & { + req: http.IncomingMessage; + }; + let data = ''; + resp.on('data', chunk => { + data += chunk; + }); + resp.on('end', () => { + resolve({ + data, + statusCode: res.statusCode, + /* tslint:disable:no-any */ + reqHeaders: (res.req as any).getHeaders + ? (res.req as any).getHeaders() + : (res.req as any)._headers, + /* tslint:enable:no-any */ + resHeaders: res.headers, + method: res.req.method, + }); + }); + resp.on('error', err => { + reject(err); + }); + }); + }); + }, +};