1 #include <mbgl/util/mapbox.hpp>
2 #include <mbgl/util/constants.hpp>
3 #include <mbgl/util/logging.hpp>
4 #include <mbgl/util/url.hpp>
5 #include <mbgl/util/tileset.hpp>
6 
7 #include <stdexcept>
8 #include <vector>
9 #include <iostream>
10 #include <cstring>
11 
12 namespace {
13 
14 const char* protocol = "mapbox://";
15 const std::size_t protocolLength = 9;
16 
17 } // namespace
18 
19 namespace mbgl {
20 namespace util {
21 namespace mapbox {
22 
isMapboxURL(const std::string & url)23 bool isMapboxURL(const std::string& url) {
24     return url.compare(0, protocolLength, protocol) == 0;
25 }
26 
equals(const std::string & str,const URL::Segment & segment,const char * ref)27 static bool equals(const std::string& str, const URL::Segment& segment, const char* ref) {
28     return str.compare(segment.first, segment.second, ref) == 0;
29 }
30 
normalizeSourceURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)31 std::string normalizeSourceURL(const std::string& baseURL,
32                                const std::string& str,
33                                const std::string& accessToken) {
34     if (!isMapboxURL(str)) {
35         return str;
36     }
37     if (accessToken.empty()) {
38         throw std::runtime_error(
39             "You must provide a Mapbox API access token for Mapbox tile sources");
40     }
41 
42     const URL url(str);
43     const auto tpl = baseURL + "/v4/{domain}.json?access_token=" + accessToken + "&secure";
44     return transformURL(tpl, str, url);
45 }
46 
normalizeStyleURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)47 std::string normalizeStyleURL(const std::string& baseURL,
48                               const std::string& str,
49                               const std::string& accessToken) {
50     if (!isMapboxURL(str)) {
51         return str;
52     }
53 
54     const URL url(str);
55     if (!equals(str, url.domain, "styles")) {
56         Log::Error(Event::ParseStyle, "Invalid style URL");
57         return str;
58     }
59 
60     const auto tpl = baseURL + "/styles/v1{path}?access_token=" + accessToken;
61     return transformURL(tpl, str, url);
62 }
63 
normalizeSpriteURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)64 std::string normalizeSpriteURL(const std::string& baseURL,
65                                const std::string& str,
66                                const std::string& accessToken) {
67     if (!isMapboxURL(str)) {
68         return str;
69     }
70 
71     const URL url(str);
72     if (!equals(str, url.domain, "sprites")) {
73         Log::Error(Event::ParseStyle, "Invalid sprite URL");
74         return str;
75     }
76 
77     const auto tpl =
78         baseURL + "/styles/v1{directory}{filename}/sprite{extension}?access_token=" + accessToken;
79     return transformURL(tpl, str, url);
80 }
81 
normalizeGlyphsURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)82 std::string normalizeGlyphsURL(const std::string& baseURL,
83                                const std::string& str,
84                                const std::string& accessToken) {
85     if (!isMapboxURL(str)) {
86         return str;
87     }
88 
89     const URL url(str);
90     if (!equals(str, url.domain, "fonts")) {
91         Log::Error(Event::ParseStyle, "Invalid glyph URL");
92         return str;
93     }
94 
95     const auto tpl = baseURL + "/fonts/v1{path}?access_token=" + accessToken;
96     return transformURL(tpl, str, url);
97 }
98 
normalizeTileURL(const std::string & baseURL,const std::string & str,const std::string & accessToken)99 std::string normalizeTileURL(const std::string& baseURL,
100                              const std::string& str,
101                              const std::string& accessToken) {
102     if (!isMapboxURL(str)) {
103         return str;
104     }
105 
106     const URL url(str);
107     if (!equals(str, url.domain, "tiles")) {
108         Log::Error(Event::ParseStyle, "Invalid tile URL");
109         return str;
110     }
111 
112     const auto tpl = baseURL + "/v4{path}?access_token=" + accessToken;
113     return transformURL(tpl, str, url);
114 }
115 
116 std::string
canonicalizeTileURL(const std::string & str,const style::SourceType type,const uint16_t tileSize)117 canonicalizeTileURL(const std::string& str, const style::SourceType type, const uint16_t tileSize) {
118     const char* version = "/v4/";
119     const size_t versionLen = strlen(version);
120 
121     const URL url(str);
122     const Path path(str, url.path.first, url.path.second);
123 
124     // Make sure that we are dealing with a valid Mapbox tile URL.
125     // Has to be /v4/, with a valid filename + extension
126     if (str.compare(url.path.first, versionLen, version) != 0 || path.filename.second == 0 ||
127         path.extension.second <= 1) {
128         // Not a proper Mapbox tile URL.
129         return str;
130     }
131 
132     // Reassemble the canonical URL from the parts we've parsed before.
133     std::string result = "mapbox://tiles/";
134     result.append(str, path.directory.first + versionLen, path.directory.second - versionLen);
135     result.append(str, path.filename.first, path.filename.second);
136     if (type == style::SourceType::Raster || type == style::SourceType::RasterDEM) {
137         result += tileSize == util::tileSize ? "@2x" : "{ratio}";
138     }
139 
140 #if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(QT_IMAGE_DECODERS)
141     const bool forceWebP = str.compare(path.extension.first, path.extension.second, ".png") == 0;
142 #else
143     const bool forceWebP = false;
144 #endif // !defined(__ANDROID__) && !defined(__APPLE__) && !defined(QT_IMAGE_DECODERS)
145 
146     // Replace PNG with WebP if necessary.
147     if (forceWebP) {
148         result += ".webp";
149     } else {
150         result.append(str, path.extension.first, path.extension.second);
151     }
152 
153     // Append the query string, minus the access token parameter.
154     if (url.query.second > 1) {
155         auto idx = url.query.first;
156         bool hasQuery = false;
157         while (idx != std::string::npos) {
158             idx++; // skip & or ?
159             auto ampersandIdx = str.find('&', idx);
160             const char* accessToken = "access_token=";
161             if (str.compare(idx, strlen(accessToken), accessToken) != 0) {
162                 result.append(1, hasQuery ? '&' : '?');
163                 result.append(str, idx, ampersandIdx != std::string::npos ? ampersandIdx - idx
164                                                                           : std::string::npos);
165                 hasQuery = true;
166             }
167             idx = ampersandIdx;
168         }
169     }
170 
171     return result;
172 }
173 
canonicalizeTileset(Tileset & tileset,const std::string & sourceURL,style::SourceType type,uint16_t tileSize)174 void canonicalizeTileset(Tileset& tileset, const std::string& sourceURL, style::SourceType type, uint16_t tileSize) {
175     // TODO: Remove this hack by delivering proper URLs in the TileJSON to begin with.
176     if (isMapboxURL(sourceURL)) {
177         for (auto& url : tileset.tiles) {
178             url = canonicalizeTileURL(url, type, tileSize);
179         }
180     }
181 }
182 
183 const uint64_t DEFAULT_OFFLINE_TILE_COUNT_LIMIT = 6000;
184 
185 } // end namespace mapbox
186 } // end namespace util
187 } // end namespace mbgl
188