|
| 1 | +// TODO: remove these once the warnings are fixed |
| 2 | +#pragma clang diagnostic push |
| 3 | +#pragma clang diagnostic ignored "-Winvalid-offsetof" |
| 4 | +#include "js/experimental/TypedData.h" // used in "js/Conversions.h" |
| 5 | +#pragma clang diagnostic pop |
| 6 | + |
| 7 | +#include "js/Conversions.h" |
| 8 | +#include "js/JSON.h" |
| 9 | + |
| 10 | +#include "builtin.h" |
| 11 | +#include "builtins/env.h" |
| 12 | +#include "builtins/fastly.h" |
| 13 | +#include "builtins/logger.h" |
| 14 | +#include "geo_ip.h" |
| 15 | + |
| 16 | +namespace builtins { |
| 17 | + |
| 18 | +bool Fastly::debug_logging_enabled = false; |
| 19 | + |
| 20 | +JS::PersistentRooted<JSObject *> Fastly::env; |
| 21 | + |
| 22 | +JS::PersistentRooted<JSObject *> Fastly::baseURL; |
| 23 | +JS::PersistentRooted<JSString *> Fastly::defaultBackend; |
| 24 | + |
| 25 | +bool Fastly::dump(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 26 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 27 | + if (!args.requireAtLeast(cx, __func__, 1)) |
| 28 | + return false; |
| 29 | + |
| 30 | + dump_value(cx, args[0], stdout); |
| 31 | + |
| 32 | + args.rval().setUndefined(); |
| 33 | + return true; |
| 34 | +} |
| 35 | + |
| 36 | +bool Fastly::enableDebugLogging(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 37 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 38 | + if (!args.requireAtLeast(cx, __func__, 1)) |
| 39 | + return false; |
| 40 | + |
| 41 | + debug_logging_enabled = JS::ToBoolean(args[0]); |
| 42 | + |
| 43 | + args.rval().setUndefined(); |
| 44 | + return true; |
| 45 | +} |
| 46 | + |
| 47 | +bool Fastly::getGeolocationForIpAddress(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 48 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 49 | + REQUEST_HANDLER_ONLY("fastly.getGeolocationForIpAddress"); |
| 50 | + if (!args.requireAtLeast(cx, "fastly.getGeolocationForIpAddress", 1)) |
| 51 | + return false; |
| 52 | + |
| 53 | + JS::RootedString address_str(cx, JS::ToString(cx, args[0])); |
| 54 | + if (!address_str) |
| 55 | + return false; |
| 56 | + |
| 57 | + JS::RootedString geo_info_str(cx, get_geo_info(cx, address_str)); |
| 58 | + if (!geo_info_str) |
| 59 | + return false; |
| 60 | + |
| 61 | + return JS_ParseJSON(cx, geo_info_str, args.rval()); |
| 62 | +} |
| 63 | + |
| 64 | +// TODO: consider allowing logger creation during initialization, but then throw |
| 65 | +// when trying to log. |
| 66 | +bool Fastly::getLogger(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 67 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 68 | + REQUEST_HANDLER_ONLY("fastly.getLogger"); |
| 69 | + JS::RootedObject self(cx, &args.thisv().toObject()); |
| 70 | + if (!args.requireAtLeast(cx, "fastly.getLogger", 1)) |
| 71 | + return false; |
| 72 | + |
| 73 | + size_t name_len; |
| 74 | + JS::UniqueChars name = encode(cx, args[0], &name_len); |
| 75 | + if (!name) |
| 76 | + return false; |
| 77 | + |
| 78 | + JS::RootedObject logger(cx, builtins::Logger::create(cx, name.get())); |
| 79 | + if (!logger) |
| 80 | + return false; |
| 81 | + |
| 82 | + args.rval().setObject(*logger); |
| 83 | + return true; |
| 84 | +} |
| 85 | + |
| 86 | +bool Fastly::includeBytes(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 87 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 88 | + INIT_ONLY("fastly.includeBytes"); |
| 89 | + JS::RootedObject self(cx, &args.thisv().toObject()); |
| 90 | + if (!args.requireAtLeast(cx, "fastly.includeBytes", 1)) |
| 91 | + return false; |
| 92 | + |
| 93 | + size_t path_len; |
| 94 | + JS::UniqueChars path = encode(cx, args[0], &path_len); |
| 95 | + if (!path) |
| 96 | + return false; |
| 97 | + |
| 98 | + FILE *fp = fopen(path.get(), "r"); |
| 99 | + if (!fp) { |
| 100 | + JS_ReportErrorUTF8(cx, "Error opening file %s", path.get()); |
| 101 | + return false; |
| 102 | + } |
| 103 | + |
| 104 | + fseek(fp, 0L, SEEK_END); |
| 105 | + size_t size = ftell(fp); |
| 106 | + rewind(fp); |
| 107 | + JS::RootedObject typed_array(cx, JS_NewUint8Array(cx, size)); |
| 108 | + if (!typed_array) |
| 109 | + return false; |
| 110 | + |
| 111 | + size_t read_bytes; |
| 112 | + { |
| 113 | + JS::AutoCheckCannotGC noGC(cx); |
| 114 | + bool is_shared; |
| 115 | + void *buffer = JS_GetArrayBufferViewData(typed_array, &is_shared, noGC); |
| 116 | + read_bytes = fread(buffer, 1, size, fp); |
| 117 | + } |
| 118 | + |
| 119 | + if (read_bytes != size) { |
| 120 | + JS_ReportErrorUTF8(cx, "Failed to read contents of file %s", path.get()); |
| 121 | + return false; |
| 122 | + } |
| 123 | + |
| 124 | + args.rval().setObject(*typed_array); |
| 125 | + return true; |
| 126 | +} |
| 127 | + |
| 128 | +const JSFunctionSpec Fastly::methods[] = { |
| 129 | + JS_FN("dump", dump, 1, 0), |
| 130 | + JS_FN("enableDebugLogging", enableDebugLogging, 1, JSPROP_ENUMERATE), |
| 131 | + JS_FN("getGeolocationForIpAddress", getGeolocationForIpAddress, 1, JSPROP_ENUMERATE), |
| 132 | + JS_FN("getLogger", getLogger, 1, JSPROP_ENUMERATE), |
| 133 | + JS_FN("includeBytes", includeBytes, 1, JSPROP_ENUMERATE), |
| 134 | + JS_FS_END}; |
| 135 | + |
| 136 | +bool Fastly::env_get(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 137 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 138 | + args.rval().setObject(*env); |
| 139 | + return true; |
| 140 | +} |
| 141 | + |
| 142 | +bool Fastly::baseURL_get(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 143 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 144 | + args.rval().setObjectOrNull(baseURL); |
| 145 | + return true; |
| 146 | +} |
| 147 | + |
| 148 | +bool Fastly::baseURL_set(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 149 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 150 | + if (args.get(0).isNullOrUndefined()) { |
| 151 | + baseURL.set(nullptr); |
| 152 | + } else if (!URL::is_instance(args.get(0))) { |
| 153 | + JS_ReportErrorUTF8(cx, "Invalid value assigned to fastly.baseURL, must be an instance of " |
| 154 | + "URL, null, or undefined"); |
| 155 | + return false; |
| 156 | + } |
| 157 | + |
| 158 | + baseURL.set(&args.get(0).toObject()); |
| 159 | + |
| 160 | + args.rval().setObjectOrNull(baseURL); |
| 161 | + return true; |
| 162 | +} |
| 163 | + |
| 164 | +bool Fastly::defaultBackend_get(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 165 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 166 | + args.rval().setString(defaultBackend); |
| 167 | + return true; |
| 168 | +} |
| 169 | + |
| 170 | +bool Fastly::defaultBackend_set(JSContext *cx, unsigned argc, JS::Value *vp) { |
| 171 | + JS::CallArgs args = CallArgsFromVp(argc, vp); |
| 172 | + JS::RootedString backend(cx, JS::ToString(cx, args.get(0))); |
| 173 | + if (!backend) |
| 174 | + return false; |
| 175 | + |
| 176 | + defaultBackend = backend; |
| 177 | + args.rval().setUndefined(); |
| 178 | + return true; |
| 179 | +} |
| 180 | + |
| 181 | +const JSPropertySpec Fastly::properties[] = { |
| 182 | + JS_PSG("env", env_get, JSPROP_ENUMERATE), |
| 183 | + JS_PSGS("baseURL", baseURL_get, baseURL_set, JSPROP_ENUMERATE), |
| 184 | + JS_PSGS("defaultBackend", defaultBackend_get, defaultBackend_set, JSPROP_ENUMERATE), JS_PS_END}; |
| 185 | + |
| 186 | +bool Fastly::create(JSContext *cx, JS::HandleObject global) { |
| 187 | + JS::RootedObject fastly(cx, JS_NewPlainObject(cx)); |
| 188 | + if (!fastly) |
| 189 | + return false; |
| 190 | + |
| 191 | + env.init(cx, Env::create(cx)); |
| 192 | + if (!env) |
| 193 | + return false; |
| 194 | + baseURL.init(cx); |
| 195 | + defaultBackend.init(cx); |
| 196 | + |
| 197 | + if (!JS_DefineProperty(cx, global, "fastly", fastly, 0)) |
| 198 | + return false; |
| 199 | + return JS_DefineFunctions(cx, fastly, methods) && JS_DefineProperties(cx, fastly, properties); |
| 200 | +} |
| 201 | + |
| 202 | +} // namespace builtins |
0 commit comments