This is the second day of my participation in Gwen Challenge
The project address implemented in this article.
It should be common practice for many companies to put some static resource files and log files of users on Seven Cows. Seven Cow also provides a graphical tool called Kodo Browser, which makes it easy for users to browse files stored on seven Cow servers.
When there is a need for customization, it can also be customized from kodo-Browser’s GitHub source code. We need to customize the tool (see Swift/object-c decryption for Meituan Logan) so that we can easily view the encrypted log files. But since I didn’t have any knowledge of Notes or anything like that, I had to go the other way, using Swift + SwiftUI to do something similar to Finder on the Mac.
If you want to access the files on Qiniu, the first thing you need to understand is how qiniu’s network protocol works. The good news is that Qiniu provides API documentation, the bad news is that I read it for a long time and couldn’t understand anything. So I had to look at the Java SDK to analyze the network protocol.
Qiniu provides SDKS of many languages, and there are few scenes of direct use of API. Therefore, I feel that Qiniu does not do a good job in the maintenance of API documents, of course, it does not rule out my limited ability.
Here are the three network protocols that Swift implements:
- Getting the Bucket list
- Get a list of files
- Obtaining File Information
import Foundation
import Moya
let RsQboxBaseURL = URL(string: "https://rs.qbox.me")!
let RsQiniuBaseURL = URL(string: "https://rs.qiniu.com")!
let RsfQiniuBaseURL = URL(string: "https://rsf.qbox.me")!
public enum QiniuRequest {
case buckets
case statInfo(bucket: String, fileKey: String?).case list(bucket: String, prefix: String)}extension QiniuRequest: TargetType {
public var baseURL: URL {
switch self {
case .buckets:
return RsQiniuBaseURL
case .statInfo:
return RsQboxBaseURL
case .list:
return RsfQiniuBaseURL}}public var path: String {
switch self {
case .list:
return "/list"
case .buckets:
return "/buckets"
case let .statInfo(bucket, fileKey):
let key = UrlSafeBase64.encodedEntry(bucket: bucket, fileKey: fileKey) ?? ""
return "/stat/\(key)"}}public var task: Task {
switch self {
case .buckets:
return .requestPlain
case .statInfo:
return .requestPlain
case .list:
return .requestParameters(parameters: getPram() ?? [String : String](), encoding: URLEncoding.default)
}
}
func getPram(a)- > [String : String]? {
switch self {
case let .list(bucket, prefix) :var pram = [String: String]()
pram["bucket"] = bucket
pram["delimiter"] = "/"
if prefix.count > 0 {
pram["prefix"] = prefix
}
return pram
default:
return nil}}public var headers: [String : String]? {
var header = [String : String] ()var absoluteString = self.absoluteURL.absoluteString
if let parameters = getPram(), parameters.count > 0 {
absoluteString + = "?"
var components: [(String.String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components + = queryComponents(fromKey: key, value: value)
}
absoluteString + = components.map { "\ [$0)=\ [The $1)" }.joined(separator: "&")}let url = URL(string: absoluteString) ?? absoluteURL
debugPrint("url:\(url)")
header["Authorization"] = QiniuTool.getToken(url: url)
return header
}
public var method: Moya.Method {
switch self {
case .statInfo,
.list,
.buckets:
return .get
}
}
}
extension QiniuRequest {
/// Creates a percent-escaped, URL encoded query string components from the given key-value pair recursively.
///
/// - Parameters:
/// - key: Key of the query component.
/// - value: Value of the query component.
///
/// - Returns: The percent-escaped, URL encoded query string components.
public func queryComponents(fromKey key: String.value: Any)- > [(String.String)] {
var components: [(String.String)] = []
switch value {
case let dictionary as [String: Any] :for (nestedKey, value) in dictionary {
components + = queryComponents(fromKey: "\(key)[\(nestedKey)]. "", value: value)
}
default:
components.append((escape(key), escape("\(value)")))}return components
}
/// Creates a percent-escaped string following RFC 3986 for a query string key or value.
///
/// - Parameter string: `String` to be percent-escaped.
///
/// - Returns: The percent-escaped `String`.
public func escape(_ string: String) -> String {
string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
}
}
Copy the code
There are three different hosts for the three network requests, which is supposed to relieve the pressure on the server.
Seven cow network protocol
The network protocol of Qiniu adds Authorization information to the header. The generation process of Authorization is as follows:
- Get the full request URL path;
- Sort all the request parameters, concatenate them after the URL path, concatenate a newline character “\n”, and encrypt them;
- The final concatenation is a string in the format “QBox QiniuAccessKey: encrypted result “.
Here is how to encrypt a string:
class QiniuTool {
static func getToken(url: URL) -> String? {
var text = ""
text + = url.path
if let query = url.query {
text + = "?\(query)"
}
text + = "\n"
var digest = text.hmac(algorithm: .SHA1, key: QiniuConfig.QiniuSecretKey)
digest = digest.replacingOccurrences(of: "/", with: "_")
digest = digest.replacingOccurrences(of: "+", with: "-")
return "QBox \(QiniuConfig.QiniuAccessKey):\(digest)"}}Copy the code
Seven cows temporary access link
To make it easy to download files, you can generate a temporary access link.
Generating a temporary access link for seven cows does not require a service request, but an encrypted token is added to the end of the access link, and the server verifies the token when accessing the server.
class QiniuTool {
static func getPublicUrl(bucket: String = QiniuConfig.BucketDomain.key: String) -> String? {
let e = Int(Date().timeIntervalSince1970 + 3600)
let url = "https://\(bucket)/\(key)? e=\(e)"
var r = url.hmac(algorithm: .SHA1, key: QiniuConfig.QiniuSecretKey)
r = r.replacingOccurrences(of: "/", with: "_")
r = r.replacingOccurrences(of: "+", with: "-")
return "\(url)&token=\(QiniuConfig.QiniuAccessKey):\(r)"}}Copy the code
I used to think that this kind of link needs to be generated on the server to be safe, but it can be generated on the client.
Once again remind
See the full project address for this article.
When there is a need for customization, you can use the GitHub source code of Kodo-Browser to customize, or use a good SDK provided by Seven Cows to save a lot of trouble.